diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml new file mode 100644 index 00000000..fcea30ed --- /dev/null +++ b/.github/workflows/ci-test.yml @@ -0,0 +1,77 @@ +name: rake test + +on: [push, pull_request] + +env: + JAVA_OPTS: '-XX:+TieredCompilation -XX:TieredStopAtLevel=1' + JRUBY_OPTS: '-J-ea' + +jobs: + + maven-test: + runs-on: ubuntu-22.04 + + strategy: + matrix: + ruby-version: [ jruby-9.4.13.0 ] + java-version: [ 8, 11, 21, 23 ] + distribution: [ temurin ] + include: + - java-version: 8 + distribution: temurin + ruby-version: jruby-9.2.19.0 + - java-version: 11 + distribution: temurin + ruby-version: jruby-9.2.20.1 + - java-version: 8 + distribution: temurin + ruby-version: jruby-9.3.3.0 + - java-version: 11 + distribution: temurin + ruby-version: jruby-9.3.13.0 + - java-version: 21 + distribution: oracle + ruby-version: jruby-9.3.13.0 + - java-version: 11 + distribution: zulu + ruby-version: jruby-9.4.5.0 + - java-version: 21 + distribution: oracle + ruby-version: jruby-9.4.8.0 + - java-version: 21 + distribution: temurin + ruby-version: jruby-10.0.1.0 + - java-version: 24 + distribution: zulu + ruby-version: jruby-10.0.1.0 + - java-version: 21 + distribution: corretto + ruby-version: jruby-head # 10.0 + fail-fast: false + + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: set up java ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: ${{ matrix.distribution }} + + - name: set up ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - name: install bundler + run: jruby -S gem install bundler -v "~>2.2.28" + + - name: bundle install + run: jruby -S bundle install + + - name: rake test_prepare + run: jruby -rbundler/setup -S rake test_prepare + + - name: rake test + run: jruby -rbundler/setup -S rake test diff --git a/.github/workflows/ci-test_provider.yml b/.github/workflows/ci-test_provider.yml new file mode 100644 index 00000000..42a17c83 --- /dev/null +++ b/.github/workflows/ci-test_provider.yml @@ -0,0 +1,56 @@ +name: rake test (with provider) + +on: [push, pull_request] + +env: + JAVA_OPTS: '-Djruby.openssl.provider.register=true -Djruby.openssl.warn=true ' + JRUBY_OPTS: '-J-ea -J--add-opens=java.base/java.security=org.jruby.dist -Xjit.threshold=0' + +jobs: + + maven-test: + runs-on: ubuntu-24.04 # ubuntu-latest + + strategy: + matrix: + ruby-version: [ jruby-9.4.12.0 ] + java-version: [ 21, 23 ] + distribution: [ temurin, oracle ] + include: + - ruby-version: jruby-9.4.8.0 + java-version: 11 + distribution: corretto + - ruby-version: jruby-9.4.8.0 + java-version: 11 + distribution: zulu + - ruby-version: jruby-9.4.8.0 + java-version: 11 + distribution: temurin + fail-fast: false + + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: set up java ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: ${{ matrix.distribution }} + + - name: set up ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - name: install bundler + run: jruby -S gem install bundler -v "~>2.2.28" + + - name: bundle install + run: jruby -S bundle install + + - name: rake test_prepare + run: jruby -rbundler/setup -S rake test_prepare + + - name: rake test + run: jruby -rbundler/setup -S rake test diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 00000000..86abdc76 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,13 @@ + + + + org.jruby.maven + mavengem-wagon + 2.0.2 + + + io.takari.polyglot + polyglot-ruby + 0.7.0 + + diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..70f4f50f --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1003fa7d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,87 +0,0 @@ -language: ruby - -dist: precise # due OpenJDK 7 -jdk: - - openjdk7 - - oraclejdk8 - -env: - - TEST_PROFILE=test-1.7.20 - - TEST_PROFILE=test-1.7.26 - - TEST_PROFILE=test-9.0.5.0 - - TEST_PROFILE=test-9.1.8.0 - - TEST_PROFILE=test-9.1.17.0 - -before_install: - - unset _JAVA_OPTIONS - - rvm @default,@global do gem uninstall bundler -a -x -I || true - - gem install bundler -v "~>1.17.3" - -install: if [[ -v BUNDLE_INSTALL ]]; then jruby -S bundle install; else echo ""; fi - -script: if [[ -v TEST_COMMAND ]]; then $TEST_COMMAND; else mvn verify -P $TEST_PROFILE; fi - -matrix: - allow_failures: - - jdk: openjdk7 - - jdk: oraclejdk11 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - - rvm: jruby-head - include: - # since maven runit fails to boot with test-unit being a default gem on 9.2 : - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-9.2.5.0 - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-9.2.9.0 - # - - jdk: openjdk7 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-1.7.24 - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-1.7.27 - - jdk: openjdk7 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-1.7.27 - # - - jdk: openjdk7 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn verify -P test-1.7.26" BUNDLE_INSTALL=true RUBY_MAVEN_VERSION=3.3.8 - rvm: jruby-1.7.26 - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-9.2.8.0 - - jdk: oraclejdk11 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-9.2.9.0 - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-head - - jdk: oraclejdk11 - env: TEST_COMMAND="jruby -rbundler/setup -S rmvn test-compile && jruby -S rake test" BUNDLE_INSTALL=true - rvm: jruby-head - # - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - rvm: jruby-1.7.26 - - jdk: openjdk7 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - rvm: jruby-1.7.27 - - jdk: oraclejdk8 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - rvm: jruby-9.2.8.0 - - jdk: openjdk7 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - rvm: jruby-9.1.17.0 - - jdk: oraclejdk11 - env: TEST_COMMAND="jruby -S rake integration:install integration:test" - rvm: jruby-9.2.9.0 -notifications: - irc: - channels: - - "irc.freenode.org#jruby" - on_success: change - template: - - "%{repository} (%{branch}:%{commit} by %{author}): %{message} (%{build_url})" - skip_join: true diff --git a/BUILDING.md b/BUILDING.md index 4a98c003..ff035177 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -10,15 +10,19 @@ If you're coming from a Ruby world and do not have Maven setup, you can alternat ### Building -The usual `mvn package` builds a .gem that includes the JRuby extension .jar +The usual `./mvnw package -Dmaven.test.skip=true` builds a .gem that includes the JRuby extension .jar +There's a rake target as well that shells out: `jruby -S rake jar` ### Testing -Tests `mvn test` are run by default with Maven using JRuby plugins. -When the ext .jar is build (`rake jar` or `mvn package -Dmaven.test.skip=true`) -one can run a tests Ruby-style e.g. `jruby -Ilib:. src/test/ruby/test_bn.rb` +NOTE: the ext .jar needs to be build (see the Building section above on `rake jar`) +The full unit test suite can be boostraped using Rake: `jruby -S rake test` + +Tests can also be run individually e.g. `jruby -Ilib:src/test/ruby src/test/ruby/test_bn.rb` + +NOTE: make sure to **-Ilib** otherwise you end up using the OpenSSL default gem shipped with JRuby. ### Releasing @@ -28,39 +32,13 @@ one can run a tests Ruby-style e.g. `jruby -Ilib:. src/test/ruby/test_bn.rb` make sure [pom.xml](pom.xml) is regenerated e.g. using `rmvn validate` and `git commit` the changes -* (optional) signing artifacts is preferred thus find your GPG key - -* `mvn -Prelease -DupdateReleaseInfo=true -Dgpg.keyname=A7A374B9 clean deploy` - -* (advised) examine and close the repository to push it to Sonatype's staging - -* (advised) run JRuby's full suite using the staged new jruby-openssl gem - e.g. https://github.com/jruby/jruby/commit/1df6315e9145195f19ad862be5e3a5 - -* (advised) release the staging repository at Sonatype's if all is well - -* (optional) update JRuby to bundle new jruby-openssl gem (remove staging) - e.g. https://github.com/jruby/jruby/commit/8750e736491825eec1d1954a07d492 +* `./mvnw -Prelease -DupdateReleaseInfo=true -Dmaven.test.skip=true clean package` * gem push the build gem from pkg/ e.g. `gem push pkg/jruby-openssl-0.9.15.gem` * tag the release e.g. `git tag v0.9.15` * update `VERSION` to next SNAPSHOT (e.g. `"0.9.16.dev"`) and commit - make sure [pom.xml](pom.xml) is regenerated (`rmvn validate`) + make sure [pom.xml](pom.xml) is regenerated (`./mvnw validate`) * `git push origin master --tags` - -* (advised) ... take the rest of the day off! - - -#### Manually Deploying - -When a release went by only pushing to http://rubygems.org/ one can still push -to Sonatype's Maven repos, rename *jruby-openssl-x.x.x-java.gem* (when it is -downloaded from https://rubygems.org/gems/jruby-openssl) to follow Maven's -naming conventions (stripping the *-java* suffix) and "mvn deploy" by hand : - -``` -mvn deploy:deploy-file -Dfile=jruby-openssl-0.9.15.gem -DpomFile=pom.xml -DrepositoryId=ossrh -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -``` \ No newline at end of file diff --git a/Gemfile b/Gemfile index 72781685..6f4de2aa 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ source 'https://rubygems.org' # Specify your gem's dependencies in the gemspec -gemspec +gemspec if defined? JRUBY_VERSION -# for less surprises with newer releases -gem 'jar-dependencies', '~> 0.3.11', :require => nil +gem "rake", require: false +gem 'mocha', '~> 1.4', '< 2.0' -# for the rake task -gem 'ruby-maven', ENV['RUBY_MAVEN_VERSION'] || '~> 3.3.8' +# NOTE: runit-maven-plugin will use it's own : +gem 'test-unit' diff --git a/History.md b/History.md index 10992eaf..d81fc667 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,264 @@ +## 0.15.5 + +* [deps] upgrade BC to version 1.81 +* Improving completeness of ASN1 encoding/decoding (#335) +* [fix] OpenSSL::X509::CRL#to_pem when building CRL from scratch (#163) +* [fix] OpenSSL::ASN1::ASN1Data encoding/decoding compatibility (#265) + +## 0.15.4 + +* Verify hostname by default + +This addresses **CVE-2025-46551** and **GHSA-72qj-48g4-5xgx**. + +Users can work around this by applying this patch manually to their +own jruby-openssl and jruby installs, or by re-enabling hostname +verification with the following code early in application boot: +```ruby +require 'openssl' + +OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:verify_hostname] = true +``` + +## 0.15.3 + +* [fix] keep curve name when group is set into another key +* [fix] make sure `OpenSSL::PKey::EC#dup` (copying) works +* [compat] make sure `OpenSSL::PKey::EC#generate_key!` exists +* [compat] missing OpenSSL:BN `to_int`, `-@`, `+@`, `abs`, `negative?` +* [compat] implement PKey::EC `public_to_pem` and `xxx_to_der` +* [fix] initialize @unused_bits = 0 for BitString +* [fix] raise ASN1Error when unused_bits out of range +* [fix] respect @unused_bits in BitString (#323) +* [fix] missing `OpenSSL::ASN1::ObjectId#==` (#311) +* [compat] implement PKey::DSA `public_to_der` and `public_to_pem` +* [compat] implement PKey::RSA `public_to_der` and `public_to_pem` +* [fix] DSA private key should generate after `set_key` +* [refactor] RSA key internals to always consider params +* [fix] DSA key compatibility when `set_pqg` +* [fix] RSA private key should generate after `set_key` +* [compat] add private? and public? methods on `PKey::EC` + +## 0.15.2 + +* [deps] upgrade BC to version 1.79 +* [fix] avoid PKey::EC.new failing with specific DER (#318) +* [fix] have a useful OPENSSL_VERSION_NUMBER + +## 0.15.1 + +* [deps] upgrade BC to version 1.78.1 + +## 0.15.0 + +This version upgraded to latest Bouncy-Castle (1.78) and the minimum supported +JRuby is now 9.2. + +* [refactor] propagate IOError from selector exception +* [fix] convert IOException to Ruby exception correctly + follow up on the fix (#242) in 0.14.6 +* [fix] implement `OpenSSL::PKey::EC::Point#mul` and `#add` (#307) +* [fix] ASN.1 BitString pad bits being out of range +* [compat] support base64digest on `OpenSSL::HMAC` +* [compat] add `Buffering#getbyte` for `SSLSocket` +* [refactor] drop (unused) Config native impl +* [refactor] less locking when there's a shared SSLContext +* [fix] encoding of ASN1::Null primitive to_der +* [fix] ASN.1 tagged object tag-class encoding/decoding +* [fix] ASN1 primitive tagging (encoding) part (#122) +* [fix] encoding/decoding of all ASN1 string types +* [fix] ASN1Data encoding with Array primitive value (#119) +* [refactor] drop security restriction JCE work-around +* [refactor] drop long deprecated OpenSSLReal Java class +* [deps] upgrade BC to version 1.78 + +## 0.14.6 + +* [compat] OpenSSL::ConfigError and DEFAULT_CONFIG_FILE (#304) +* [fix] `OpenSSL::PKey::DH#set_pqg` regression (#300) +* Convert `IOException` to Ruby exception correctly (#242) +* [refactor] add exception debugging within SSLSocket#waitSelect +* [fix] sync `SSLContext#setup` as it could be shared (#302) +* [refactor] organize i-var sets (set `@context` after setup) + +## 0.14.5 + +* [fix] `OpenSSL::X509::Request#verify` with DSA public key + (this was a regression introduced in JOSSL 0.14.4) + +## 0.14.4 + +* [fix] convert `OpenSSL::ASN1::Sequence` to an array on #to_der (#265) +* [feat] implement `PKey::DH.generate` and (dummy) `q` reader (#254) +* [fix] raise `TypeError` when arg isn't a `Group` +* [refactor] make sure `ASN1Error` has native cause +* [fix] stop assuming (JDK) EC key identifier + "EC" with Sun provider but "ECDSA" with BC +* [fix] do not check empty string as curve name +* [fix] make sure `PKeyEC#group.curve_name` is always set +* [refactor] `PKey.read` to use BC fully when reading public keys +* [fix] `OpenSSL::X509::CRL#sign` to accept string digest +* [fix] `OpenSSL::X509::Request#version` default is -1 +* [fix] resolving EC key from `X509::Request.new(pem)` +* [feat] implement `OpenSSL::X509::Request#signature_algorithm` +* [fix] work-around CSR failing with EC key (#294) +* [feat] implement `OpenSSL::PKey::EC#to_text` (#280) +* [feat] partial support for `PKey::EC::Point#to_octet_string(form)` +* [feat] implement `OpenSSL::PKCS7::SignerInfo#signed_time` (#269) +* [feat] implement #oid method for `PKey` classes (#281) +* [fix] raise `PKeyError` from `PKey.read` when no key (#285) +* [fix] restore PKCS#8 EC key handling (see #292) +* [fix] revert `readPrivateKey` so public key is not lost (#292) + +## 0.14.3 + +* [fix] `SSLSocket#alpn_protocol` to be nil when not used (#287) +* [feat] try resolving curve-name from EC public key +* [feat] implement missing `PKey::EC#dsa_verify_asn1` (#241) +* [feat] implement support for `PKey::EC.generate` (#255) +* [refactor] make sure curveName is set when using `PKey.read` (#289) +* [fix] add `Cipher#auth_data(arg)` override (Rails 7.x compatibility) (#290) +* [fix] raise `TypeError` when arg not of expected type (jruby/jruby#7875) + +## 0.14.2 + +* [deps] upgrade BC to latest 1.74 +* [fix] for CRL verify when signed with EC key (#276) +* [fix] `OpenSSL::X509::Certificate#public_key` raises for EC keys (#273) + +## 0.14.1 + +* [refactor] improve performance of Diffie-Hellman key exchange (#272) +* Try to use JDK console to prompt for pass (#270) +* [fix] for PKCS8 EC private key support (#267) +* ~~"[fix] handle potential buffer overflow on write" (#242)~~ + +## 0.14.1 (CR2) + +* [fix] Java's default session timeout in 24h +* [fix] handle ArgumentError on `SSLSession#timeout=` +* [fix] handle potential buffer overflow on write (#242) +* [fix] buffer overflow after wrap-ing data - wait +* [refactor] try a few tricks to detect session re-use + +## 0.14.0 + +This version upgraded to latest Bouncy-Castle (1.71) and is only compatible with +the new version mostly due artifact naming and breaking chances in BC itself. + +* [deps] upgrade BC to latest 1.71 +* [fix] make set_minmax_proto_version private + +## 0.13.0 + +* [fix] ASN1::EndOfContent ancestor hierarchy (#228) +* [fix] handle X509::Name type conversion (#206) +* [fix] handle invalid type when creating `X509::Name` +* [fix] `OpenSSL::X509::Name#inspect` compatibility +* [fix] escaping with `OpenSSL::X509::Name::RFC2253` +* [feat] implement `OpenSSL::X509::Name#to_utf8` +* [fix] compat missing `OpenSSL::SSL::OP_NO_TLSv1_3` +* [refactor] performance - do not encode/decode cert objects +* [fix] make sure `Context.ciphers` are not mutated (#219) +* [feat] support `to_java` conversion for CRL +* [feat] support `to_java` protocol for PKey (#250) + +## 0.12.2 + +* [fix] work-around JRuby 9.2 autoload behavior (#248) + to be able to install jruby-openssl >= 0.12 on JRuby 9.2 + while the default gem (shipped with JRuby) is < 0.12 +* [feat] support alpn negotiation in ssl context (#247) +* [feat] support Java cipher names on `SSLContext#ciphers=` +* [fix] properly handle `require_jar` fallback + +## 0.12.1 + +* improved compatibility with the openssl gem (version 2.2.1) +* JOSSL now ships with a single set of openssl .rb files + - providing compat with `required_ruby_version = '>= 2.3.0'` + - flat set of .rb files at *lib/openssl/* (based on openssl gem) +* revisited `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS` defaults + - implicit `verify_hostname` default .rb callback still a noop + - TLS continues to rely on the Java SSL engine for hostname checks +* working TLS 1.3 support +* droped Java 1.7 support (at least Java 8 needed to use the gem) +* fixed `SSLContext#options` matches C OpenSSL (using `OP_ALL`) +* no longer filter out SSLv2 (for improved OpenSSL compatibility) +* implemented naive `SSLContext#ciphers` caching to speed-up TLS +* `StoreError` raised due a Java exception now retain native cause + +## 0.12.0 (yanked) + +There were Java 8 and JRuby 9.3 regressions in this release, use 0.12.1 instead. + +## 0.11.0 + +NOTE: This release aims to adapt the certificate verification logic to be aligned +with OpenSSL 1.1.1 as a resolution to issues due *DST Root CA X3* expiration, more +details at: https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/ + +The port is expected to be superior compared to the simple legacy verification, +however in case of issues the previous algorithm is still around and can be toggled +using `JRUBY_OPTS="-J-Djruby.openssl.x509.store.verify=legacy"` system property. + +* **OpenSSL 1.1.1 cert verification port** (fixes #236) (#239) + - as a side-effect part of the PR to "allow multiple certs with same SubjectDN" + (#198) got reverted, this has been causing verification regressions (since 0.10.5) + for some users (#232) and is expected to be fixed +* [fix] replace deprecated getPeerCertificateChain (#231) + +## 0.10.7 + +* [feat] upgrade BC library to 1.68 +* [fix] SSLContext#ciphers= (fixes #221 and jruby/jruby#3100) (#222) +* [fix] Java::JavaLang::StringIndexOutOfBoundsException on ctx.cipher=[] (fixes #220) (#223) +* [fix] SSLContext#ciphers= compatibility (fixes #223) (#220) +* [fix] Match OpenSSL::X509::Name.hash implementation with Ruby (#216, #218) +* [fix] OpenSSL::SSL::SSLContext#min_version= failure (#215) +* [fix] adds OpenSSL::Cipher#iv_len= setter (#208) + +## 0.10.6 (yanked) + +Due several regressions please update to version 0.10.7 or higher. + +## 0.10.5 + +* [fix] EC key sign/verify (#193) +* [feat] upgrade BC library to 1.65 +* [refactor] clean security helpers to avoid reflection (#197) +* Just use normal getInstance to get KeyFactory (fixes #197) +* Allow multiple Certificates with the same SubjectDN in the store (#198) +* Try direct path for MessageDigest before invasive path (#194) + (relates to jruby/jruby#6098) +* [refactor] avoid NativeException usage (jruby/jruby#5646) + +## 0.10.4 + +* Use CertificateFactory.getInstance rather than reflection + eliminates one of the module warnings we have been seeing (#161) + +## 0.10.3 + +* [fix] implement (missing) PKey::DSA#params +* [fix] authorityKeyIdentifier ext (general-name) value +* [fix] authority keyid extension's :always part optional (#174) +* [fix] work-around for not setting certificate serial + raise a more friendly error (jruby/jruby#1691) +* [fix] PKey.read not parsing RSA pub-key (#176) +* [feat] support reading DSA (public key) in full DER +* [fix] RSA key DER format to closely follow OpenSSL +* [fix] add missing ASN1 factory methods (Null, EndOfContent) +* [fix] support getting password from block for PKeys +* [fix] incorrect ASN.1 for wrapped Integer type +* [fix] correct public key for subjectKeyIdentifier ext (#173) +* [fix] invalid Cert#sign handling -> raise (instead of ClassCastException) +* [feat] more TLS (GCM) ciphers - supported on Java 8+ +* [feat] add ECDHE-RSA-AES128-GCM-SHA256 as supported cipher (#185) +* [feat] add support for ECDHE-RSA-AES256-GCM-SHA384 (#187) +* [fix] try hard not to fail on unkown oids (OpenSSL::X509::Certificate#to_text) +* update Bouncy-Castle to 1.62 (and handle supported BC compatibility) + ## 0.10.2 * update Bouncy-Castle to 1.61 (and handle supported BC compatibility) diff --git a/LICENSE.txt b/LICENSE.txt index 1b78015c..8f2413b4 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -18,7 +18,7 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Copyright (C) 2007-2009 Ola Bini - Copyright (C) 2009-2018 The JRuby Team + Copyright (C) 2009-2024 The JRuby Team Alternatively, the contents of this file may be used under the terms of either of the GNU General Public License Version 2 or later (the "GPL"), diff --git a/Mavenfile b/Mavenfile index f14cc84e..25e36a7b 100644 --- a/Mavenfile +++ b/Mavenfile @@ -7,7 +7,7 @@ distribution_management do repository :id => :ossrh, :url => 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' end -java_target = '1.7' +java_target = '1.8' gen_sources = '${basedir}/target/generated-sources' # hard-coded in AnnotationBinder plugin( 'org.codehaus.mojo:exec-maven-plugin', '1.3.2' ) do @@ -45,15 +45,18 @@ plugin( 'org.codehaus.mojo:build-helper-maven-plugin', '1.9' ) do execute_goal 'add-source', :phase => 'process-classes', :sources => [ gen_sources ] end -plugin( :compiler, '3.1', - :source => '1.7', :target => java_target, - :encoding => 'UTF-8', :debug => true, - :showWarnings => true, :showDeprecation => true, - :excludes => [ 'module-info.java' ], +compiler_configuration = { + :source => '1.8', :target => java_target, :release => '8', + :encoding => 'UTF-8', :debug => true, + :showWarnings => true, :showDeprecation => true, + :excludes => [ 'module-info.java' ], + #:jdkToolchain => { :version => '[1.7,11)' }, + :generatedSourcesDirectory => gen_sources, + :annotationProcessors => [ 'org.jruby.anno.AnnotationBinder' ] +} +compiler_configuration.delete(:release) if ENV_JAVA['java.specification.version'] == '1.8' - :generatedSourcesDirectory => gen_sources, - :annotationProcessors => [ 'org.jruby.anno.AnnotationBinder' ], - :compilerArgs => [ '-XDignore.symbol.file=true' ] ) do +plugin( :compiler, '3.9.0', compiler_configuration) do #execute_goal :compile, :id => 'annotation-binder', :phase => 'compile', # :generatedSourcesDirectory => gen_sources, #:outputDirectory => gen_sources, @@ -62,15 +65,11 @@ plugin( :compiler, '3.1', # :useIncrementalCompilation => false, :fork => true, :verbose => true, # :compilerArgs => [ '-XDignore.symbol.file=true', '-J-Dfile.encoding=UTF-8' ] - execute_goal :compile, :id => 'compile-populators', :phase => 'process-classes', - :includes => [ 'org/jruby/gen/**/*.java' ], :optimize => true, - :compilerArgs => [ '-XDignore.symbol.file=true' ] - # NOTE: maybe '-J-Xbootclasspath/p:${unsafe.jar}' ... as well ?! -end - -profile 'module-info' do - activation { jdk '[9,)' } - plugin :compiler, '3.1', :source => '9', :target => java_target, :includes => [ 'module-info.java' ] + execute_goal :compile, + :id => 'compile-populators', :phase => 'process-classes', + :includes => [ 'org/jruby/gen/**/*.java' ], + :optimize => true, + :compilerArgs => [ '', '-XDignore.symbol.file=true' ] end plugin :clean do @@ -83,13 +82,18 @@ plugin :clean do 'failOnError' => 'false' ) end -jar 'org.jruby:jruby-core', '1.7.20', :scope => :provided -jar 'junit:junit', '4.11', :scope => :test +jar 'org.jruby:jruby-core', '9.1.11.0', :scope => :provided +# for invoker generated classes we need to add javax.annotation when on Java > 8 +jar 'javax.annotation:javax.annotation-api', '1.3.1', :scope => :compile +jar 'junit:junit', '[4.13.1,)', :scope => :test + +# NOTE: to build on Java 11 - installing gems fails (due old jossl) with: +# load error: jopenssl/load -- java.lang.StringIndexOutOfBoundsException +MVN_JRUBY_VERSION = ENV_JAVA['java.version'].to_i >= 9 ? '9.2.19.0' : '9.1.17.0' jruby_plugin! :gem do # when installing dependent gems we want to use the built in openssl not the one from this lib directory - # we compile against jruby-core-1.7.20 and want to keep this out of the plugin execution here - execute_goal :id => 'default-initialize', :addProjectClasspath => false, :libDirectory => 'something-which-does-not-exists' + execute_goal :id => 'default-package', :addProjectClasspath => false, :libDirectory => 'something-which-does-not-exists' execute_goals :id => 'default-push', :skip => true end @@ -98,24 +102,24 @@ plugin :deploy, '2.8.1' do execute_goals( :deploy, :skip => false ) end -supported_bc_versions = %w{ 1.55 1.56 1.57 1.58 1.59 1.60 1.61 } +supported_bc_versions = %w{ 1.60 1.61 1.62 1.63 1.64 1.65 1.66 1.67 1.68 } default_bc_version = File.read File.expand_path('lib/jopenssl/version.rb', File.dirname(__FILE__)) default_bc_version = default_bc_version[/BOUNCY_CASTLE_VERSION\s?=\s?'(.*?)'/, 1] -properties( 'jruby.plugins.version' => '1.1.6', - 'jruby.versions' => '9.1.17.0', +properties( 'jruby.plugins.version' => '3.0.2', 'jruby.switches' => '-W0', # https://github.com/torquebox/jruby-maven-plugins/issues/94 'bc.versions' => default_bc_version, 'invoker.test' => '${bc.versions}', # allow to skip all tests with -Dmaven.test.skip 'invoker.skip' => '${maven.test.skip}', 'runit.dir' => 'src/test/ruby/**/test_*.rb', - # use this version of jruby for ALL the jruby-maven-plugins - 'jruby.version' => '9.1.17.0', # Java 7 compatible till supporting JRuby 1.7 - # dump pom.xml as readonly when running 'rmvn' - 'polyglot.dump.pom' => 'pom.xml', - 'polyglot.dump.readonly' => true ) + 'mavengem.wagon.version' => '2.0.2', # for jruby plugin + 'mavengem-wagon.version' => '2.0.2', # for polyglot-ruby + # use this version of jruby for the jruby-maven-plugins + 'jruby.versions' => MVN_JRUBY_VERSION, 'jruby.version' => MVN_JRUBY_VERSION, + # dump pom.xml when running 'rmvn' + 'polyglot.dump.pom' => 'pom.xml', 'polyglot.dump.readonly' => false ) # make sure we have the embedded jars in place before we run runit plugin plugin! :dependency do @@ -142,21 +146,8 @@ invoker_run_options = { 'runit.dir' => '${runit.dir}' } } -jruby_1_7_versions = %w{ 1.7.20 1.7.22 1.7.23 1.7.24 1.7.25 1.7.26 1.7.27 } - -jruby_1_7_versions.each { |version| -profile :id => "test-#{version}" do - plugin :invoker, '1.8' do - execute_goals( :install, :run, invoker_run_options ) - end - properties 'jruby.versions' => version, - 'jruby.modes' => '1.9,2.0', - 'bc.versions' => supported_bc_versions.join(',') -end -} - -jruby_9_K_versions = %w{ 9.0.1.0 9.0.5.0 9.1.2.0 9.1.8.0 9.1.12.0 9.1.16.0 9.1.17.0 } -jruby_9_K_versions += %w{ 9.2.0.0 9.2.5.0 9.2.6.0 } +jruby_9_K_versions = %w{ 9.1.2.0 9.1.8.0 9.1.12.0 9.1.16.0 9.1.17.0 } +jruby_9_K_versions += %w{ 9.2.0.0 9.2.5.0 9.2.10.0 9.2.17.0 9.2.19.0 } jruby_9_K_versions.each { |version| profile :id => "test-#{version}" do diff --git a/README.md b/README.md index 477ec5f4..fc82183a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # JRuby-OpenSSL [JRuby-OpenSSL](https://github.com/jruby/jruby-openssl) is an add-on gem for -[JRuby](http://jruby.org) that emulates the Ruby OpenSSL native library. +[JRuby](https://www.jruby.org/) that emulates the Ruby OpenSSL native library. -Under the hood uses the [Bouncy Castle Crypto APIs](http://www.bouncycastle.org/). +Under the hood uses the [Bouncy Castle Crypto APIs](https://www.bouncycastle.org/java.html). Each jruby-openssl gem release includes a certain version, usually the latest available, of the library (namely BC Provider and PKIX/CMS/EAC/PKCS/OCSP/TSP/OPENSSL jars). @@ -13,23 +13,29 @@ the JRuby [mailing list][1] or the [bug tracker][2]. ## Compatibility - -| JRuby-OpenSSL | JRuby compat | JVM compat | supported BC | -| ------------- |:-------------:| ----------:| ------------:| -| 0.9.6 | 1.6.8-9.0.2 | Java 6-8 | 1.47-1.50 | -| 0.9.12 | 1.6.8-9.0.5 | Java 6-8 | 1.47-1.52 | -| 0.9.13 | 1.6.8-9.1.2 | Java 6-8 | 1.49-1.52 | -| 0.9.14 | 1.6.8-9.1.5 | Java 6-8 | 1.49-1.54 | -| 0.9.17 | 1.6.8-9.1.5 | Java 6-8 | 1.50-1.54 | -| ~>0.9.18 | 1.6.8-9.1.x | Java 6-8 | 1.50-1.55 | -| 0.10.0 | 1.7.20-9.2.x | Java 7-10 | 1.55-1.59 | +| JRuby-OpenSSL | JRuby compat | JVM compat | supported BC | +|---------------|:------------:|-----------:|-------------:| +| 0.9.6 | 1.6.8-9.0.2 | Java 6-8 | 1.47-1.50 | +| 0.9.12 | 1.6.8-9.0.5 | Java 6-8 | 1.47-1.52 | +| 0.9.13 | 1.6.8-9.1.2 | Java 6-8 | 1.49-1.52 | +| 0.9.14 | 1.6.8-9.1.5 | Java 6-8 | 1.49-1.54 | +| 0.9.17 | 1.6.8-9.1.5 | Java 6-8 | 1.50-1.54 | +| ~>0.9.18 | 1.6.8-9.1.x | Java 6-8 | 1.50-1.55 | +| 0.10.0 | 1.7.20-9.2.x | Java 7-10 | 1.55-1.59 | +| 0.10.3 | 1.7.20-9.2.x | Java 7-11 | 1.56-1.62 | +| ~>0.10.5 | 1.7.20-9.3.x | Java 7-11 | 1.60-1.68 | +| ~>0.11.x | 9.0.x-9.3.x | Java 7-11 | 1.62-1.68 | +| ~>0.12.x | 9.1.x-9.3.x | Java 8-15 | 1.65-1.68 | +| ~>0.13.x | 9.1.x-9.4.x | Java 8-17 | 1.68-1.69 | +| ~>0.14.x | 9.1.x-9.4.x | Java 8-21 | 1.71-1.74 | +| ~>0.15.x | 9.2.x-9.4.x | Java 8-21 | 1.76-1.79 | NOTE: backwards JRuby compatibility was not handled for versions <= **0.9.6** ## Security -JRuby-OpenSSL is an essential part of [JRuby](http://jruby.org), please report security -vulnerabilities to `security@jruby.org` as detailed on JRuby's [security page](http://jruby.org/security). +JRuby-OpenSSL is an essential part of [JRuby](https://www.jruby.org/), please report security vulnerabilities to +`security@jruby.org` as detailed on JRuby's [security page](https://www.jruby.org/security) or using [GitHub][0]. Please note that most OpenSSL vulnerabilities do not effect JRuby since its not using any of OpenSSL's C code, only Ruby parts (*.rb) are the same as in MRI's OpenSSL library. @@ -42,32 +48,28 @@ any of OpenSSL's C code, only Ruby parts (*.rb) are the same as in MRI's OpenSSL mvn test will run (junit as well as ruby) tests and a some ruby tests against the default -jruby version. to pick a different version and/or modes (1.8, 1.9, 2.0, 2.1) run +jruby version. to pick a different JRuby version run - mvn test -Djruby.versions=1.7.12 -Djruby.modes=1.8 + mvn test -Djruby.versions=9.2.8.0 for running integration-tests the gem will be first installed and then the same tests run for each possible bouncy-castle version (see [listing][3]), run with - mvn verify -P test-9.0.4.0,test-1.7.22 + mvn verify -P test-9.2.9.0,test-9.1.17.0 or pick a bouncy-castle version - mvn verify -P test-1.6.8 -Dbc.versions=1.50 - -or simply be more picky - - mvn verify -P test-1.7.4 -Dbc.versions=1.49 -Djruby.modes=1.9 + mvn verify -P test-9.2.9.0 -Dbc.versions=1.60 NOTE: you can pick any jruby version which is on [central][4] or on [ci.jruby][5] ## License -(c) 2009-2018 JRuby distributed under EPL 1.0/GPL 2.0/LGPL 2.1 +(c) 2009-2024 JRuby distributed under EPL 1.0/GPL 2.0/LGPL 2.1 -[0]: https://secure.travis-ci.org/jruby/jruby-openssl.svg -[1]: http://xircles.codehaus.org/projects/jruby/lists -[2]: https://github.com/jruby/jruby/issues +[0]: https://github.com/jruby/jruby-openssl/security +[1]: https://github.com/jruby/jruby/wiki/MailingLists +[2]: https://github.com/jruby/jruby-openssl/issues/new [3]: https://github.com/jruby/jruby-openssl/tree/master/integration [4]: http://central.maven.org/maven2/org/jruby/ -[5]: http://ci.jruby.org/snapshots/maven/org.jruby/ +[5]: https://www.jruby.org/nightly diff --git a/Rakefile b/Rakefile index 67c47b25..ef83d8f5 100644 --- a/Rakefile +++ b/Rakefile @@ -1,36 +1,27 @@ #-*- mode: ruby -*- -begin - require 'ruby-maven' -rescue LoadError - warn "ruby-maven not available - some tasks will not work " << - "either `gem install ruby-maven' or use mvn instead of rake" - desc "Package jopenssl.jar with the compiled classes" - task :jar do - sh "mvn prepare-package -Dmaven.test.skip=true" - end - namespace :jar do - desc "Package jopenssl.jar file (and dependendent jars)" - task :all do - sh "mvn package -Dmaven.test.skip=true" - end - end -else - #Rake::Task[:jar].clear rescue nil - desc "Package jopenssl.jar with the compiled classes" - task :jar do - RubyMaven.exec( 'prepare-package -Dmaven.test.skip=true' ) - end - namespace :jar do - desc "Package jopenssl.jar file (and dependendent jars)" - task :all do - RubyMaven.exec( 'package -Dmaven.test.skip=true' ) - end +#Rake::Task[:jar].clear rescue nil +desc "Package jopenssl.jar with the compiled classes" +task :jar do + sh( './mvnw prepare-package -Dmaven.test.skip=true' ) +end +namespace :jar do + desc "Package jopenssl.jar file (and dependendent jars)" + task :all do + sh( './mvnw package -Dmaven.test.skip=true' ) end end +task :test_prepare do + sh( './mvnw prepare-package -Dmaven.test.skip=true' ) + sh( './mvnw test-compile' ) # separate step due -Dmaven.test.skip=true +end + +task :clean do + sh( './mvnw clean' ) +end task :build do - RubyMaven.exec('package -Dmaven.test.skip') + sh( './mvnw clean package -Dmaven.test.skip=true' ) end task :default => :build @@ -51,16 +42,15 @@ task :test => 'lib/jopenssl.jar' namespace :integration do it_path = File.expand_path('../src/test/integration', __FILE__) task :install do - Dir.chdir(it_path) do - ruby "-S bundle install --gemfile '#{it_path}/Gemfile'" - end + ruby "-C #{it_path} -S bundle install" end # desc "Run IT tests" task :test => 'lib/jopenssl.jar' do unless File.exist?(File.join(it_path, 'Gemfile.lock')) raise "bundle not installed, run `rake integration:install'" end - loader = "ARGV.each { |f| require f }" ; lib = [ 'lib', it_path ] + loader = "ARGV.each { |f| require f }" + lib = [ File.expand_path('../lib', __FILE__), it_path ] test_files = FileList['src/test/integration/*_test.rb'].map { |path| path.sub('src/test/integration/', '') } ruby "-I#{lib.join(':')} -C src/test/integration -e \"#{loader}\" #{test_files.map { |f| "\"#{f}\"" }.join(' ')}" end diff --git a/integration/pom.xml b/integration/pom.xml index bc0941a9..1f0a89a2 100644 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -26,13 +26,13 @@ true jruby - http://ci.jruby.org/snapshots/maven + https://ci.jruby.org/snapshots/maven rubygems-releases - http://rubygems-proxy.torquebox.org/releases + https://rubygems-proxy.torquebox.org/releases diff --git a/jruby-openssl.gemspec b/jruby-openssl.gemspec index 42affb8c..195ccd9e 100644 --- a/jruby-openssl.gemspec +++ b/jruby-openssl.gemspec @@ -25,19 +25,13 @@ Gem::Specification.new do |s| bc_version = version_rb.match( /.*\sBOUNCY_CASTLE_VERSION\s*=\s*['"](.*)['"]/ )[1] raise 'BOUNCY_CASTLE_VERSION not matched' if (bc_version || '').empty? - s.requirements << "jar org.bouncycastle:bcprov-jdk15on, #{bc_version}" # Provider - s.requirements << "jar org.bouncycastle:bcpkix-jdk15on, #{bc_version}" # PKIX/CMS/EAC/PKCSOCSP/TSP/OPENSSL - s.requirements << "jar org.bouncycastle:bctls-jdk15on, #{bc_version}" # DTLS/TLS API/JSSE Provider + s.required_ruby_version = '>= 2.5.0' # JRuby >= 9.2 - s.required_ruby_version = '>= 1.9.3' - s.required_rubygems_version = '>= 2.4.8' + s.requirements << "jar org.bouncycastle:bcprov-jdk18on, #{bc_version}" # Provider + s.requirements << "jar org.bouncycastle:bcpkix-jdk18on, #{bc_version}" # PKIX/CMS/EAC/PKCSOCSP/TSP/OPENSSL + s.requirements << "jar org.bouncycastle:bctls-jdk18on, #{bc_version}" # DTLS/TLS API/JSSE Provider + s.requirements << "jar org.bouncycastle:bcutil-jdk18on, #{bc_version}" - s.add_development_dependency 'jar-dependencies', '~> 0.1' - - s.add_development_dependency 'mocha', '~> 1.4', '< 2.0' - s.add_development_dependency 'ruby-maven', '~> 3.0' - # NOTE: runit-maven-plugin will use it's own : - #s.add_development_dependency 'test-unit', '2.5.5' end # vim: syntax=Ruby diff --git a/lib/jopenssl/_compat23.rb b/lib/jopenssl/_compat23.rb deleted file mode 100644 index 8da642c6..00000000 --- a/lib/jopenssl/_compat23.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: false - -module OpenSSL - - module PKey - - class DH - - def set_key(pub_key, priv_key) - self.pub_key = pub_key - self.priv_key = priv_key - self - end - - def set_pqg(p, q, g) - self.p = p - if respond_to?(:q) - self.q = q - else # TODO self.q = q - OpenSSL.warn "JRuby-OpenSSL does not support setting q param on #{inspect}" if q - end - self.g = g - self - end - - end - - class DSA - - def set_key(pub_key, priv_key) - self.pub_key = pub_key - self.priv_key = priv_key - self - end - - def set_pqg(p, q, g) - self.p = p - self.q = q - self.g = g - self - end - - end - - class RSA - - def set_key(n, e, d) - self.n = n - self.e = e - self.d = d - self - end - - def set_factors(p, q) - self.p = p - self.q = q - self - end - - def set_crt_params(dmp1, dmq1, iqmp) - self.dmp1 = dmp1 - self.dmq1 = dmq1 - self.iqmp = iqmp - self - end - - end - - end - -end diff --git a/lib/jopenssl/load.rb b/lib/jopenssl/load.rb index 6e681ccc..6d38b8a7 100644 --- a/lib/jopenssl/load.rb +++ b/lib/jopenssl/load.rb @@ -1,50 +1,71 @@ -warn 'Loading jruby-openssl gem in a non-JRuby interpreter' unless defined? JRUBY_VERSION - require 'jopenssl/version' -warn "JRuby #{JRUBY_VERSION} is not supported by jruby-openssl #{JOpenSSL::VERSION}" if JRUBY_VERSION < '1.7.20' - # NOTE: assuming user does pull in BC .jars from somewhere else on the CP unless ENV_JAVA['jruby.openssl.load.jars'].eql?('false') version = JOpenSSL::BOUNCY_CASTLE_VERSION - bc_jars = nil begin require 'jar-dependencies' # if we have jar-dependencies we let it track the jars - require_jar( 'org.bouncycastle', 'bcprov-jdk15on', version ) - require_jar( 'org.bouncycastle', 'bcpkix-jdk15on', version ) - require_jar( 'org.bouncycastle', 'bctls-jdk15on', version ) + require_jar 'org.bouncycastle', 'bcprov-jdk18on', version + require_jar 'org.bouncycastle', 'bcpkix-jdk18on', version + require_jar 'org.bouncycastle', 'bcutil-jdk18on', version + require_jar 'org.bouncycastle', 'bctls-jdk18on', version bc_jars = true - rescue LoadError + rescue LoadError, RuntimeError bc_jars = false end unless bc_jars - load "org/bouncycastle/bcprov-jdk15on/#{version}/bcprov-jdk15on-#{version}.jar" - load "org/bouncycastle/bcpkix-jdk15on/#{version}/bcpkix-jdk15on-#{version}.jar" - load "org/bouncycastle/bctls-jdk15on/#{version}/bctls-jdk15on-#{version}.jar" + load "org/bouncycastle/bcprov-jdk18on/#{version}/bcprov-jdk18on-#{version}.jar" + load "org/bouncycastle/bcpkix-jdk18on/#{version}/bcpkix-jdk18on-#{version}.jar" + load "org/bouncycastle/bcutil-jdk18on/#{version}/bcutil-jdk18on-#{version}.jar" + load "org/bouncycastle/bctls-jdk18on/#{version}/bctls-jdk18on-#{version}.jar" end end require 'jopenssl.jar' +JRuby::Util.load_ext('org.jruby.ext.openssl.OpenSSL') -if JRuby::Util.respond_to?(:load_ext) # JRuby 9.2 - JRuby::Util.load_ext('org.jruby.ext.openssl.OpenSSL') -else; require 'jruby' - org.jruby.ext.openssl.OpenSSL.load(JRuby.runtime) -end - -if RUBY_VERSION > '2.3' - load 'jopenssl23/openssl.rb' - load 'jopenssl/_compat23.rb' -elsif RUBY_VERSION > '2.2' - load 'jopenssl22/openssl.rb' -elsif RUBY_VERSION > '2.1' - load 'jopenssl21/openssl.rb' -else - load 'jopenssl19/openssl.rb' -end +# NOTE: content bellow should live in *lib/openssl.rb* but due RubyGems/Bundler +# `autoload :OpenSSL` this will cause issues if an older version (0.11) is the +# default gem under JRuby 9.2 (which on auto-load does not trigger a dynamic +# require - this is only fixed in JRuby 9.3) module OpenSSL autoload :Config, 'openssl/config' unless const_defined?(:Config, false) + autoload :ConfigError, 'openssl/config' unless const_defined?(:ConfigError, false) autoload :PKCS12, 'openssl/pkcs12' end + +=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 'LICENCE'.) +=end + +require 'openssl/bn' +require 'openssl/pkey' +require 'openssl/cipher' +require 'openssl/digest' +require 'openssl/hmac' +require 'openssl/x509' +require 'openssl/ssl' +require 'openssl/pkcs5' + +module OpenSSL + # call-seq: + # OpenSSL.secure_compare(string, string) -> boolean + # + # 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. + 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 diff --git a/lib/jopenssl/version.rb b/lib/jopenssl/version.rb index 30fa8c27..d5766787 100644 --- a/lib/jopenssl/version.rb +++ b/lib/jopenssl/version.rb @@ -1,9 +1,10 @@ module JOpenSSL - VERSION = '0.10.3.dev' - BOUNCY_CASTLE_VERSION = '1.62' + VERSION = '0.15.6.dev' + BOUNCY_CASTLE_VERSION = '1.81' end Object.class_eval do Jopenssl = JOpenSSL private_constant :Jopenssl if respond_to?(:private_constant) + deprecate_constant :Jopenssl if respond_to?(:deprecate_constant) end diff --git a/lib/jopenssl19/openssl.rb b/lib/jopenssl19/openssl.rb deleted file mode 100644 index 3efdd1ee..00000000 --- a/lib/jopenssl19/openssl.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin -= $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2002 Michal Rokos - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -require 'openssl/bn' -require 'openssl/cipher' -require 'openssl/config' -require 'openssl/digest' -require 'openssl/ssl-internal' -require 'openssl/x509-internal' diff --git a/lib/jopenssl19/openssl/bn.rb b/lib/jopenssl19/openssl/bn.rb deleted file mode 100644 index 3933ee3c..00000000 --- a/lib/jopenssl19/openssl/bn.rb +++ /dev/null @@ -1,29 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = 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 licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -## -# Add double dispatch to Integer -# -class Integer - def to_bn - OpenSSL::BN::new(self) - end -end # Integer - diff --git a/lib/jopenssl19/openssl/buffering.rb b/lib/jopenssl19/openssl/buffering.rb deleted file mode 100644 index 51bc968e..00000000 --- a/lib/jopenssl19/openssl/buffering.rb +++ /dev/null @@ -1,449 +0,0 @@ -=begin -= $RCSfile$ -- Buffering mix-in module. - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2001 GOTOU YUUZOU - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -## -# OpenSSL IO buffering mix-in module. -# -# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO. - -module OpenSSL::Buffering - include Enumerable - - ## - # The "sync mode" of the SSLSocket. - # - # See IO#sync for full details. - - attr_accessor :sync - - ## - # Default size to read from or write to the SSLSocket for buffer operations. - - BLOCK_SIZE = 1024*16 - - def initialize(*args) - @eof = false - @rbuffer = "" - @sync = @io.sync - end - - # - # for reading. - # - private - - ## - # Fills the buffer from the underlying SSLSocket - - def fill_rbuff - begin - @rbuffer << self.sysread(BLOCK_SIZE) - rescue Errno::EAGAIN - retry - rescue EOFError - @eof = true - end - end - - ## - # Consumes +size+ bytes from the buffer - - def consume_rbuff(size=nil) - if @rbuffer.empty? - nil - else - size = @rbuffer.size unless size - ret = @rbuffer[0, size] - @rbuffer[0, size] = "" - ret - end - end - - public - - ## - # Reads +size+ bytes from the stream. If +buf+ is provided it must - # reference a string which will receive the data. - # - # See IO#read for full details. - - def read(size=nil, buf=nil) - if size == 0 - if buf - buf.clear - return buf - else - return "" - end - end - until @eof - break if size && size <= @rbuffer.size - fill_rbuff - end - ret = consume_rbuff(size) || "" - if buf - buf.replace(ret) - ret = buf - end - (size && ret.empty?) ? nil : ret - end - - ## - # Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it - # must reference a string which will receive the data. - # - # See IO#readpartial for full details. - - def readpartial(maxlen, buf=nil) - if maxlen == 0 - if buf - buf.clear - return buf - else - return "" - end - end - if @rbuffer.empty? - begin - return sysread(maxlen, buf) - rescue Errno::EAGAIN - retry - end - end - ret = consume_rbuff(maxlen) - if buf - buf.replace(ret) - ret = buf - end - raise EOFError if ret.empty? - 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 - - def read_nonblock(maxlen, buf=nil) - if maxlen == 0 - if buf - buf.clear - return buf - else - return "" - end - end - if @rbuffer.empty? - return sysread_nonblock(maxlen, buf) - end - ret = consume_rbuff(maxlen) - if buf - buf.replace(ret) - ret = buf - end - raise EOFError if ret.empty? - ret - end - - ## - # Reads the next "line+ from the stream. Lines are separated by +eol+. If - # +limit+ is provided the result will not be longer than the given number of - # bytes. - # - # +eol+ may be a String or Regexp. - # - # Unlike IO#gets the line read will not be assigned to +$_+. - # - # Unlike IO#gets the separator must be provided if a limit is provided. - - def gets(eol=$/, limit=nil) - idx = @rbuffer.index(eol) - until @eof - break if idx - fill_rbuff - idx = @rbuffer.index(eol) - end - if eol.is_a?(Regexp) - size = idx ? idx+$&.size : nil - else - size = idx ? idx+eol.size : nil - end - if limit and limit >= 0 - size = [size, limit].min - end - consume_rbuff(size) - end - - ## - # Executes the block for every line in the stream where lines are separated - # by +eol+. - # - # See also #gets - - def each(eol=$/) - while line = self.gets(eol) - yield line - end - end - alias each_line each - - ## - # Reads lines from the stream which are separated by +eol+. - # - # See also #gets - - def readlines(eol=$/) - ary = [] - while line = self.gets(eol) - ary << line - end - ary - end - - ## - # Reads a line from the stream which is separated by +eol+. - # - # Raises EOFError if at end of file. - - def readline(eol=$/) - raise EOFError if eof? - gets(eol) - end - - ## - # Reads one character from the stream. Returns nil if called at end of - # file. - - def getc - read(1) - end - - ## - # Calls the given block once for each byte in the stream. - - def each_byte # :yields: byte - while c = getc - yield(c.ord) - end - end - - ## - # Reads a one-character string from the stream. Raises an EOFError at end - # of file. - - def readchar - raise EOFError if eof? - getc - end - - ## - # Pushes character +c+ back onto the stream such that a subsequent buffered - # character read will return it. - # - # Unlike IO#getc multiple bytes may be pushed back onto the stream. - # - # Has no effect on unbuffered reads (such as #sysread). - - def ungetc(c) - @rbuffer[0,0] = c.chr - end - - ## - # Returns true if the stream is at file which means there is no more data to - # be read. - - def eof? - fill_rbuff if !@eof && @rbuffer.empty? - @eof && @rbuffer.empty? - end - alias eof eof? - - # - # for writing. - # - private - - ## - # Writes +s+ to the buffer. When the buffer is full or #sync is true the - # buffer is flushed to the underlying socket. - - def do_write(s) - @wbuffer = "" unless defined? @wbuffer - @wbuffer << s - @wbuffer.force_encoding(Encoding::BINARY) - @sync ||= false - if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/) - remain = idx ? idx + $/.size : @wbuffer.length - nwritten = 0 - while remain > 0 - str = @wbuffer[nwritten,remain] - begin - nwrote = syswrite(str) - rescue Errno::EAGAIN - retry - end - remain -= nwrote - nwritten += nwrote - end - @wbuffer[0,nwritten] = "" - end - end - - public - - ## - # Writes +s+ to the stream. If the argument is not a string it will be - # converted using String#to_s. Returns the number of bytes written. - - def write(s) - do_write(s) - s.bytesize - end - - ## - # Writes +str+ 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 - - def write_nonblock(s) - flush - syswrite_nonblock(s) - end - - ## - # Writes +s+ to the stream. +s+ will be converted to a String using - # String#to_s. - - def << (s) - do_write(s) - self - end - - ## - # Writes +args+ to the stream along with a record separator. - # - # See IO#puts for full details. - - def puts(*args) - s = "" - if args.empty? - s << "\n" - end - args.each{|arg| - s << arg.to_s - if $/ && /\n\z/ !~ s - s << "\n" - end - } - do_write(s) - nil - end - - ## - # Writes +args+ to the stream. - # - # See IO#print for full details. - - def print(*args) - s = "" - args.each{ |arg| s << arg.to_s } - do_write(s) - nil - end - - ## - # Formats and writes to the stream converting parameters under control of - # the format string. - # - # See Kernel#sprintf for format string details. - - def printf(s, *args) - do_write(s % args) - nil - end - - ## - # Flushes buffered data to the SSLSocket. - - def flush - osync = @sync - @sync = true - do_write "" - return self - ensure - @sync = osync - end - - ## - # Closes the SSLSocket and flushes any unwritten data. - - def close - flush rescue nil - sysclose - end -end diff --git a/lib/jopenssl19/openssl/cipher.rb b/lib/jopenssl19/openssl/cipher.rb deleted file mode 100644 index b254a238..00000000 --- a/lib/jopenssl19/openssl/cipher.rb +++ /dev/null @@ -1,28 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = Ruby-space predefined Cipher subclasses -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - class Cipher - # This class is only provided for backwards compatibility. Use OpenSSL::Cipher in the future. - class Cipher < Cipher - # add warning - end - end # Cipher -end # OpenSSL \ No newline at end of file diff --git a/lib/jopenssl19/openssl/config.rb b/lib/jopenssl19/openssl/config.rb deleted file mode 100644 index 5716d59f..00000000 --- a/lib/jopenssl19/openssl/config.rb +++ /dev/null @@ -1,472 +0,0 @@ -=begin -= Ruby-space definitions that completes C-space funcs for Config - -= Info - Copyright (C) 2010 Hiroshi Nakamura - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -=end - -require 'stringio' - -module OpenSSL - ## - # = OpenSSL::Config - # - # Configuration for the openssl library. - # - # Many system's installation of openssl library will depend on your system - # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for - # the location of the file for your host. - # - # See also http://www.openssl.org/docs/apps/config.html - class Config - include Enumerable - - class << self - - ## - # Parses a given +string+ as a blob that contains configuration for openssl. - # - # If the source of the IO is a file, then consider using #parse_config. - def parse(string) - c = new() - parse_config(StringIO.new(string)).each do |section, hash| - c[section] = hash - end - c - end - - ## - # load is an alias to ::new - alias load new - - ## - # Parses the configuration data read from +io+, see also #parse. - # - # Raises a ConfigError on invalid configuration data. - def parse_config(io) - begin - parse_config_lines(io) - rescue ConfigError => e - e.message.replace("error in line #{io.lineno}: " + e.message) - raise - end - end - - def get_key_string(data, section, key) # :nodoc: - if v = data[section] && data[section][key] - return v - elsif section == 'ENV' - if v = ENV[key] - return v - end - end - if v = data['default'] && data['default'][key] - return v - end - end - - private - - def parse_config_lines(io) - section = 'default' - data = {section => {}} - while definition = get_definition(io) - definition = clear_comments(definition) - next if definition.empty? - if definition[0] == ?[ - if /\[([^\]]*)\]/ =~ definition - section = $1.strip - data[section] ||= {} - else - raise ConfigError, "missing close square bracket" - end - else - if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition - if $2 - section = $1 - key = $2 - else - key = $1 - end - value = unescape_value(data, section, $3) - (data[section] ||= {})[key] = value.strip - else - raise ConfigError, "missing equal sign" - end - end - end - data - end - - # escape with backslash - QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ - # escape with backslash and doubled dq - QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ - # escaped char map - ESCAPE_MAP = { - "r" => "\r", - "n" => "\n", - "b" => "\b", - "t" => "\t", - } - - def unescape_value(data, section, value) - scanned = [] - while m = value.match(/['"\\$]/) - scanned << m.pre_match - c = m[0] - value = m.post_match - case c - when "'" - if m = value.match(QUOTE_REGEXP_SQ) - scanned << m[1].gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when '"' - if m = value.match(QUOTE_REGEXP_DQ) - scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when "\\" - c = value.slice!(0, 1) - scanned << (ESCAPE_MAP[c] || c) - when "$" - ref, value = extract_reference(value) - refsec = section - if ref.index('::') - refsec, ref = ref.split('::', 2) - end - if v = get_key_string(data, refsec, ref) - scanned << v - else - raise ConfigError, "variable has no value" - end - else - raise 'must not reaced' - end - end - scanned << value - scanned.join - end - - def extract_reference(value) - rest = '' - if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) - value = m[1] || m[2] - rest = m.post_match - elsif [?(, ?{].include?(value[0]) - raise ConfigError, "no close brace" - end - if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) - return m[0], m.post_match + rest - else - raise - end - end - - def clear_comments(line) - # FCOMMENT - if m = line.match(/\A([\t\n\f ]*);.*\z/) - return m[1] - end - # COMMENT - scanned = [] - while m = line.match(/[#'"\\]/) - scanned << m.pre_match - c = m[0] - line = m.post_match - case c - when '#' - line = nil - break - when "'", '"' - regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ - scanned << c - if m = line.match(regexp) - scanned << m[0] - line = m.post_match - else - scanned << line - line = nil - break - end - when "\\" - scanned << c - scanned << line.slice!(0, 1) - else - raise 'must not reaced' - end - end - scanned << line - scanned.join - end - - def get_definition(io) - if line = get_line(io) - while /[^\\]\\\z/ =~ line - if extra = get_line(io) - line += extra - else - break - end - end - return line.strip - end - end - - def get_line(io) - if line = io.gets - line.gsub(/[\r\n]*/, '') - end - end - end - - ## - # Creates an instance of OpenSSL's configuration class. - # - # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config= - # - # If the optional +filename+ parameter is provided, then it is read in and - # parsed via #parse_config. - # - # This can raise IO exceptions based on the access, or availability of the - # file. A ConfigError exception may be raised depending on the validity of - # the data being configured. - # - def initialize(filename = nil) - @data = {} - if filename - File.open(filename.to_s) do |file| - Config.parse_config(file).each do |section, hash| - self[section] = hash - end - end - end - end - - ## - # Gets the value of +key+ from the given +section+ - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a specific value from the config if you know the +section+ - # and +key+ like so: - # - # config.get_value('default','foo') - # #=> "bar" - # - def get_value(section, key) - if section.nil? - raise TypeError.new('nil not allowed') - end - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # - # *Deprecated* - # - # Use #get_value instead - def value(arg1, arg2 = nil) # :nodoc: - warn('Config#value is deprecated; use Config#get_value') - if arg2.nil? - section, key = 'default', arg1 - else - section, key = arg1, arg2 - end - section ||= 'default' - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # Set the target +key+ with a given +value+ under a specific +section+. - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can set the value of +foo+ under the +default+ section to a new - # value: - # - # config.add_value('default', 'foo', 'buzz') - # #=> "buzz" - # puts config.to_s - # #=> [ default ] - # # foo=buzz - # - def add_value(section, key, value) - check_modify - (@data[section] ||= {})[key] = value - end - - ## - # Get a specific +section+ from the current configuration - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a hash of the specific section like so: - # - # config['default'] - # #=> {"foo"=>"bar"} - # - def [](section) - @data[section] || {} - end - - ## - # Deprecated - # - # Use #[] instead - def section(name) # :nodoc: - warn('Config#section is deprecated; use Config#[]') - @data[name] || {} - end - - ## - # Sets a specific +section+ name with a Hash +pairs+ - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> # - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # It's important to note that this will essentially merge any of the keys - # in +pairs+ with the existing +section+. For example: - # - # config['default'] - # #=> {"foo"=>"bar", "baz"=>"buz"} - # config['default'] = {"foo" => "changed"} - # #=> {"foo"=>"changed"} - # config['default'] - # #=> {"foo"=>"changed", "baz"=>"buz"} - # - def []=(section, pairs) - check_modify - @data[section] ||= {} - pairs.each do |key, value| - self.add_value(section, key, value) - end - end - - ## - # Get the names of all sections in the current configuration - def sections - @data.keys - end - - ## - # Get the parsable form of the current configuration - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> # - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # You can parse get the serialized configuration using #to_s and then parse - # it later: - # - # serialized_config = config.to_s - # # much later... - # new_config = OpenSSL::Config.parse(serialized_config) - # #=> # - # puts new_config - # #=> [ default ] - # foo=bar - # baz=buz - # - def to_s - ary = [] - @data.keys.sort.each do |section| - ary << "[ #{section} ]\n" - @data[section].keys.each do |key| - ary << "#{key}=#{@data[section][key]}\n" - end - ary << "\n" - end - ary.join - end - - ## - # For a block. - # - # Receive the section and its pairs for the current configuration. - # - # config.each do |section, key, value| - # # ... - # end - # - def each - @data.each do |section, hash| - hash.each do |key, value| - yield [section, key, value] - end - end - end - - ## - # String representation of this configuration object, including the class - # name and its sections. - def inspect - "#<#{self.class.name} sections=#{sections.inspect}>" - end - - protected - - def data # :nodoc: - @data - end - - private - - def initialize_copy(other) - @data = other.data.dup - end - - def check_modify - raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? - end - - def get_key_string(section, key) - Config.get_key_string(@data, section, key) - end - end -end diff --git a/lib/jopenssl19/openssl/digest.rb b/lib/jopenssl19/openssl/digest.rb deleted file mode 100644 index d62993b2..00000000 --- a/lib/jopenssl19/openssl/digest.rb +++ /dev/null @@ -1,32 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = Ruby-space predefined Digest subclasses -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - class Digest - # This class is only provided for backwards compatibility. Use OpenSSL::Digest in the future. - class Digest < Digest - def initialize(*args) - # add warning - super(*args) - end - end - end # Digest -end # OpenSSL - diff --git a/lib/jopenssl19/openssl/ssl-internal.rb b/lib/jopenssl19/openssl/ssl-internal.rb deleted file mode 100644 index f8dce80c..00000000 --- a/lib/jopenssl19/openssl/ssl-internal.rb +++ /dev/null @@ -1,223 +0,0 @@ -=begin -= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for SSL - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2001 GOTOU YUUZOU - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -require "openssl/buffering" -require 'fcntl' # used by OpenSSL::SSL::Nonblock (if loaded) - -module OpenSSL - module SSL - class SSLContext - DEFAULT_PARAMS = { - :ssl_version => "SSLv23", - :verify_mode => OpenSSL::SSL::VERIFY_PEER, - :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 - ECDHE-ECDSA-RC4-SHA - ECDHE-RSA-RC4-SHA - RC4-SHA - }.join(":"), - :options => -> { - opts = OpenSSL::SSL::OP_ALL - opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) - opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) - opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) - opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) - opts - }.call - } unless const_defined? :DEFAULT_PARAMS # JRuby does it in Java - - begin - DEFAULT_CERT_STORE = OpenSSL::X509::Store.new - DEFAULT_CERT_STORE.set_default_paths - if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - end - end unless const_defined? :DEFAULT_CERT_STORE - - def set_params(params={}) - params = DEFAULT_PARAMS.merge(params) - params.each{|name, value| self.__send__("#{name}=", value) } - if self.verify_mode != OpenSSL::SSL::VERIFY_NONE - unless self.ca_file or self.ca_path or self.cert_store - self.cert_store = DEFAULT_CERT_STORE - end - end - return params - end unless method_defined? :set_params - end - - module SocketForwarder - def addr - to_io.addr - end - - def peeraddr - to_io.peeraddr - end - - def setsockopt(level, optname, optval) - to_io.setsockopt(level, optname, optval) - end - - def getsockopt(level, optname) - to_io.getsockopt(level, optname) - end - - def fcntl(*args) - to_io.fcntl(*args) - end - - def closed? - to_io.closed? - end - - def do_not_reverse_lookup=(flag) - to_io.do_not_reverse_lookup = flag - end - end - - def verify_certificate_identity(cert, hostname) - should_verify_common_name = true - cert.extensions.each { |ext| - next if ext.oid != "subjectAltName" - ext.value.split(/,\s+/).each { |general_name| - # MRI 1.9.3 (since we parse ASN.1 differently) - # when 2 # dNSName in GeneralName (RFC5280) - if /\ADNS:(.*)/ =~ general_name - should_verify_common_name = false - reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+") - return true if /\A#{reg}\z/i =~ hostname - # MRI 1.9.3 (since we parse ASN.1 differently) - # when 7 # iPAddress in GeneralName (RFC5280) - elsif /\AIP(?: Address)?:(.*)/ =~ general_name - should_verify_common_name = false - return true if $1 == hostname - # NOTE: bellow logic makes little sense as we read exts differently - #value = $1 # follows GENERAL_NAME_print() in x509v3/v3_alt.c - #if value.size == 4 - # return true if value.unpack('C*').join('.') == hostname - #elsif value.size == 16 - # return true if value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname - #end - end - } - } - if should_verify_common_name - cert.subject.to_a.each { |oid, value| - if oid == "CN" - reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+") - return true if /\A#{reg}\z/i =~ hostname - end - } - end - return false - end - module_function :verify_certificate_identity - - class SSLSocket - include Buffering - include SocketForwarder - include Nonblock - - def sysclose - return if closed? - stop - io.close if sync_close - end unless method_defined? :sysclose - - def post_connection_check(hostname) - unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname) - raise SSLError, "hostname does not match the server certificate" - end - return true - end - - end - - class SSLServer - include SocketForwarder - attr_accessor :start_immediately - - def initialize(svr, ctx) - @svr = svr - @ctx = ctx - unless ctx.session_id_context - session_id = OpenSSL::Digest::MD5.hexdigest($0) - @ctx.session_id_context = session_id - end - @start_immediately = true - end - - def to_io - @svr - end - - def listen(backlog=5) - @svr.listen(backlog) - end - - def shutdown(how=Socket::SHUT_RDWR) - @svr.shutdown(how) - end - - def accept - sock = @svr.accept - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) - ssl.sync_close = true - ssl.accept if @start_immediately - ssl - rescue SSLError => ex - sock.close - raise ex - end - end - - def close - @svr.close - end - end - end -end diff --git a/lib/jopenssl19/openssl/ssl.rb b/lib/jopenssl19/openssl/ssl.rb deleted file mode 100644 index 15f42d60..00000000 --- a/lib/jopenssl19/openssl/ssl.rb +++ /dev/null @@ -1,2 +0,0 @@ -warn 'deprecated openssl/ssl use: require "openssl" instead of "openssl/ssl"' -require 'openssl' diff --git a/lib/jopenssl19/openssl/x509-internal.rb b/lib/jopenssl19/openssl/x509-internal.rb deleted file mode 100644 index 24eeff7d..00000000 --- a/lib/jopenssl19/openssl/x509-internal.rb +++ /dev/null @@ -1,115 +0,0 @@ -=begin -= $RCSfile$ -- 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 licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -module OpenSSL - module X509 - class Name - module RFC2253DN - Special = ',=+<>#;' - HexChar = /[0-9a-fA-F]/ - HexPair = /#{HexChar}#{HexChar}/ - HexString = /#{HexPair}+/ - Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/ - StringChar = /[^#{Special}\\"]/ - QuoteChar = /[^\\"]/ - AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/ - AttributeValue = / - (?!["#])((?:#{StringChar}|#{Pair})*)| - \#(#{HexString})| - "((?:#{QuoteChar}|#{Pair})*)" - /x - TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/ - - module_function - - def expand_pair(str) - return nil unless str - return str.gsub(Pair){ - pair = $& - case pair.size - when 2 then pair[1,1] - when 3 then Integer("0x#{pair[1,2]}").chr - else raise OpenSSL::X509::NameError, "invalid pair: #{str}" - end - } - end - - def expand_hexstring(str) - return nil unless str - der = str.gsub(HexPair){$&.to_i(16).chr } - a1 = OpenSSL::ASN1.decode(der) - return a1.value, a1.tag - end - - def expand_value(str1, str2, str3) - value = expand_pair(str1) - value, tag = expand_hexstring(str2) unless value - value = expand_pair(str3) unless value - return value, tag - end - - def scan(dn) - str = dn - ary = [] - while true - if md = TypeAndValue.match(str) - remain = md.post_match - type = md[1] - value, tag = expand_value(md[2], md[3], md[4]) rescue nil - if value - type_and_value = [type, value] - type_and_value.push(tag) if tag - ary.unshift(type_and_value) - if remain.length > 2 && remain[0] == ?, - str = remain[1..-1] - next - elsif remain.length > 2 && remain[0] == ?+ - raise OpenSSL::X509::NameError, - "multi-valued RDN is not supported: #{dn}" - elsif remain.empty? - break - end - end - end - msg_dn = dn[0, dn.length - str.length] + " =>" + str - raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}" - end - return ary - end - end - - class << self - def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) - ary = OpenSSL::X509::Name::RFC2253DN.scan(str) - self.new(ary, template) - end - - def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) - ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } - self.new(ary, template) - end - - alias parse parse_openssl - end - end - - class StoreContext - def cleanup - warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE - end - end - end -end diff --git a/lib/jopenssl19/openssl/x509.rb b/lib/jopenssl19/openssl/x509.rb deleted file mode 100644 index f1777cdf..00000000 --- a/lib/jopenssl19/openssl/x509.rb +++ /dev/null @@ -1,2 +0,0 @@ -warn 'deprecated openssl/x509 use: require "openssl" instead of "openssl/x509"' -require 'openssl' diff --git a/lib/jopenssl21/openssl.rb b/lib/jopenssl21/openssl.rb deleted file mode 100644 index b1d7406c..00000000 --- a/lib/jopenssl21/openssl.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin -= $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2002 Michal Rokos - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -require 'openssl/bn' -require 'openssl/cipher' -require 'openssl/config' -require 'openssl/digest' -require 'openssl/x509' -require 'openssl/ssl' diff --git a/lib/jopenssl21/openssl/bn.rb b/lib/jopenssl21/openssl/bn.rb deleted file mode 100644 index a8a8d8ac..00000000 --- a/lib/jopenssl21/openssl/bn.rb +++ /dev/null @@ -1,28 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = 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 licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -## -# Add double dispatch to Integer -# -class Integer - def to_bn - OpenSSL::BN::new(self) - end -end # Integer diff --git a/lib/jopenssl21/openssl/buffering.rb b/lib/jopenssl21/openssl/buffering.rb deleted file mode 100644 index f6133b54..00000000 --- a/lib/jopenssl21/openssl/buffering.rb +++ /dev/null @@ -1 +0,0 @@ -load 'jopenssl22/openssl/buffering.rb' \ No newline at end of file diff --git a/lib/jopenssl21/openssl/cipher.rb b/lib/jopenssl21/openssl/cipher.rb deleted file mode 100644 index 14a02292..00000000 --- a/lib/jopenssl21/openssl/cipher.rb +++ /dev/null @@ -1 +0,0 @@ -load 'jopenssl22/openssl/cipher.rb' \ No newline at end of file diff --git a/lib/jopenssl21/openssl/config.rb b/lib/jopenssl21/openssl/config.rb deleted file mode 100644 index 613e2e8f..00000000 --- a/lib/jopenssl21/openssl/config.rb +++ /dev/null @@ -1 +0,0 @@ -load 'jopenssl22/openssl/config.rb' \ No newline at end of file diff --git a/lib/jopenssl21/openssl/digest.rb b/lib/jopenssl21/openssl/digest.rb deleted file mode 100644 index ebdeba29..00000000 --- a/lib/jopenssl21/openssl/digest.rb +++ /dev/null @@ -1 +0,0 @@ -load 'jopenssl22/openssl/digest.rb' \ No newline at end of file diff --git a/lib/jopenssl21/openssl/ssl.rb b/lib/jopenssl21/openssl/ssl.rb deleted file mode 100644 index e02f25e9..00000000 --- a/lib/jopenssl21/openssl/ssl.rb +++ /dev/null @@ -1 +0,0 @@ -load 'jopenssl22/openssl/ssl.rb' \ No newline at end of file diff --git a/lib/jopenssl21/openssl/x509.rb b/lib/jopenssl21/openssl/x509.rb deleted file mode 100644 index eabd676e..00000000 --- a/lib/jopenssl21/openssl/x509.rb +++ /dev/null @@ -1,119 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = 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 licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - module X509 - class Name - module RFC2253DN - Special = ',=+<>#;' - HexChar = /[0-9a-fA-F]/ - HexPair = /#{HexChar}#{HexChar}/ - HexString = /#{HexPair}+/ - Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/ - StringChar = /[^#{Special}\\"]/ - QuoteChar = /[^\\"]/ - AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/ - AttributeValue = / - (?!["#])((?:#{StringChar}|#{Pair})*)| - \#(#{HexString})| - "((?:#{QuoteChar}|#{Pair})*)" - /x - TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/ - - module_function - - def expand_pair(str) - return nil unless str - return str.gsub(Pair){ - pair = $& - case pair.size - when 2 then pair[1,1] - when 3 then Integer("0x#{pair[1,2]}").chr - else raise OpenSSL::X509::NameError, "invalid pair: #{str}" - end - } - end - - def expand_hexstring(str) - return nil unless str - der = str.gsub(HexPair){$&.to_i(16).chr } - a1 = OpenSSL::ASN1.decode(der) - return a1.value, a1.tag - end - - def expand_value(str1, str2, str3) - value = expand_pair(str1) - value, tag = expand_hexstring(str2) unless value - value = expand_pair(str3) unless value - return value, tag - end - - def scan(dn) - str = dn - ary = [] - while true - if md = TypeAndValue.match(str) - remain = md.post_match - type = md[1] - value, tag = expand_value(md[2], md[3], md[4]) rescue nil - if value - type_and_value = [type, value] - type_and_value.push(tag) if tag - ary.unshift(type_and_value) - if remain.length > 2 && remain[0] == ?, - str = remain[1..-1] - next - elsif remain.length > 2 && remain[0] == ?+ - raise OpenSSL::X509::NameError, - "multi-valued RDN is not supported: #{dn}" - elsif remain.empty? - break - end - end - end - msg_dn = dn[0, dn.length - str.length] + " =>" + str - raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}" - end - return ary - end - end - - class << self - def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) - ary = OpenSSL::X509::Name::RFC2253DN.scan(str) - self.new(ary, template) - end - - def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) - ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } - self.new(ary, template) - end - - alias parse parse_openssl - end - end - - class StoreContext - def cleanup - warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE - end - end - end -end diff --git a/lib/jopenssl22/openssl.rb b/lib/jopenssl22/openssl.rb deleted file mode 100644 index b1d7406c..00000000 --- a/lib/jopenssl22/openssl.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin -= $RCSfile$ -- Loader for all OpenSSL C-space and Ruby-space definitions - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2002 Michal Rokos - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -require 'openssl/bn' -require 'openssl/cipher' -require 'openssl/config' -require 'openssl/digest' -require 'openssl/x509' -require 'openssl/ssl' diff --git a/lib/jopenssl22/openssl/bn.rb b/lib/jopenssl22/openssl/bn.rb deleted file mode 100644 index 781ae9c8..00000000 --- a/lib/jopenssl22/openssl/bn.rb +++ /dev/null @@ -1,39 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = 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 licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - class BN - 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 - def to_bn - OpenSSL::BN::new(self) - end -end # Integer diff --git a/lib/jopenssl22/openssl/buffering.rb b/lib/jopenssl22/openssl/buffering.rb deleted file mode 100644 index 5248ab45..00000000 --- a/lib/jopenssl22/openssl/buffering.rb +++ /dev/null @@ -1,456 +0,0 @@ -# coding: binary -#-- -#= $RCSfile$ -- Buffering mix-in module. -# -#= Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2001 GOTOU YUUZOU -# All rights reserved. -# -#= Licence -# This program is licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -#= Version -# $Id$ -#++ - -## -# OpenSSL IO buffering mix-in module. -# -# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO. -# -# You typically won't use this module directly, you can see it implemented in -# OpenSSL::SSL::SSLSocket. - -module OpenSSL::Buffering - include Enumerable - - ## - # The "sync mode" of the SSLSocket. - # - # See IO#sync for full details. - - attr_accessor :sync - - ## - # Default size to read from or write to the SSLSocket for buffer operations. - - BLOCK_SIZE = 1024*16 - - ## - # Creates an instance of OpenSSL's buffering IO module. - - def initialize(*) - @eof = false - @rbuffer = "" - @sync = @io.sync - end - - # - # for reading. - # - private - - ## - # Fills the buffer from the underlying SSLSocket - - def fill_rbuff - begin - @rbuffer << self.sysread(BLOCK_SIZE) - rescue Errno::EAGAIN - retry - rescue EOFError - @eof = true - end - end - - ## - # Consumes +size+ bytes from the buffer - - def consume_rbuff(size=nil) - if @rbuffer.empty? - nil - else - size = @rbuffer.size unless size - ret = @rbuffer[0, size] - @rbuffer[0, size] = "" - ret - end - end - - public - - ## - # Reads +size+ bytes from the stream. If +buf+ is provided it must - # reference a string which will receive the data. - # - # See IO#read for full details. - - def read(size=nil, buf=nil) - if size == 0 - if buf - buf.clear - return buf - else - return "" - end - end - until @eof - break if size && size <= @rbuffer.size - fill_rbuff - end - ret = consume_rbuff(size) || "" - if buf - buf.replace(ret) - ret = buf - end - (size && ret.empty?) ? nil : ret - end - - ## - # Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it - # must reference a string which will receive the data. - # - # See IO#readpartial for full details. - - def readpartial(maxlen, buf=nil) - if maxlen == 0 - if buf - buf.clear - return buf - else - return "" - end - end - if @rbuffer.empty? - begin - return sysread(maxlen, buf) - rescue Errno::EAGAIN - retry - end - end - ret = consume_rbuff(maxlen) - if buf - buf.replace(ret) - ret = buf - end - raise EOFError if ret.empty? - 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 - - 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 - raise EOFError if ret.empty? - ret - end - - ## - # Reads the next "line+ from the stream. Lines are separated by +eol+. If - # +limit+ is provided the result will not be longer than the given number of - # bytes. - # - # +eol+ may be a String or Regexp. - # - # Unlike IO#gets the line read will not be assigned to +$_+. - # - # Unlike IO#gets the separator must be provided if a limit is provided. - - def gets(eol=$/, limit=nil) - idx = @rbuffer.index(eol) - until @eof - break if idx - fill_rbuff - idx = @rbuffer.index(eol) - end - if eol.is_a?(Regexp) - size = idx ? idx+$&.size : nil - else - size = idx ? idx+eol.size : nil - end - if limit and limit >= 0 - size = [size, limit].min - end - consume_rbuff(size) - end - - ## - # Executes the block for every line in the stream where lines are separated - # by +eol+. - # - # See also #gets - - def each(eol=$/) - while line = self.gets(eol) - yield line - end - end - alias each_line each - - ## - # Reads lines from the stream which are separated by +eol+. - # - # See also #gets - - def readlines(eol=$/) - ary = [] - while line = self.gets(eol) - ary << line - end - ary - end - - ## - # Reads a line from the stream which is separated by +eol+. - # - # Raises EOFError if at end of file. - - def readline(eol=$/) - raise EOFError if eof? - gets(eol) - end - - ## - # Reads one character from the stream. Returns nil if called at end of - # file. - - def getc - read(1) - end - - ## - # Calls the given block once for each byte in the stream. - - def each_byte # :yields: byte - while c = getc - yield(c.ord) - end - end - - ## - # Reads a one-character string from the stream. Raises an EOFError at end - # of file. - - def readchar - raise EOFError if eof? - getc - end - - ## - # Pushes character +c+ back onto the stream such that a subsequent buffered - # character read will return it. - # - # Unlike IO#getc multiple bytes may be pushed back onto the stream. - # - # Has no effect on unbuffered reads (such as #sysread). - - def ungetc(c) - @rbuffer[0,0] = c.chr - end - - ## - # Returns true if the stream is at file which means there is no more data to - # be read. - - def eof? - fill_rbuff if !@eof && @rbuffer.empty? - @eof && @rbuffer.empty? - end - alias eof eof? - - # - # for writing. - # - private - - ## - # Writes +s+ to the buffer. When the buffer is full or #sync is true the - # buffer is flushed to the underlying socket. - - def do_write(s) - @wbuffer = "" unless defined? @wbuffer - @wbuffer << s - @wbuffer.force_encoding(Encoding::BINARY) - @sync ||= false - if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex($/) - remain = idx ? idx + $/.size : @wbuffer.length - nwritten = 0 - while remain > 0 - str = @wbuffer[nwritten,remain] - begin - nwrote = syswrite(str) - rescue Errno::EAGAIN - retry - end - remain -= nwrote - nwritten += nwrote - end - @wbuffer[0,nwritten] = "" - end - end - - public - - ## - # Writes +s+ to the stream. If the argument is not a string it will be - # converted using String#to_s. Returns the number of bytes written. - - def write(s) - do_write(s) - s.bytesize - end - - ## - # Writes +str+ 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 - - def write_nonblock(s, exception: true) - flush - syswrite_nonblock(s, exception: exception) - end - - ## - # Writes +s+ to the stream. +s+ will be converted to a String using - # String#to_s. - - def << (s) - do_write(s) - self - end - - ## - # Writes +args+ to the stream along with a record separator. - # - # See IO#puts for full details. - - def puts(*args) - s = "" - if args.empty? - s << "\n" - end - args.each{|arg| - s << arg.to_s - if $/ && /\n\z/ !~ s - s << "\n" - end - } - do_write(s) - nil - end - - ## - # Writes +args+ to the stream. - # - # See IO#print for full details. - - def print(*args) - s = "" - args.each{ |arg| s << arg.to_s } - do_write(s) - nil - end - - ## - # Formats and writes to the stream converting parameters under control of - # the format string. - # - # See Kernel#sprintf for format string details. - - def printf(s, *args) - do_write(s % args) - nil - end - - ## - # Flushes buffered data to the SSLSocket. - - def flush - osync = @sync - @sync = true - do_write "" - return self - ensure - @sync = osync - end - - ## - # Closes the SSLSocket and flushes any unwritten data. - - def close - flush rescue nil - sysclose - end -end diff --git a/lib/jopenssl22/openssl/cipher.rb b/lib/jopenssl22/openssl/cipher.rb deleted file mode 100644 index b254a238..00000000 --- a/lib/jopenssl22/openssl/cipher.rb +++ /dev/null @@ -1,28 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = Ruby-space predefined Cipher subclasses -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - class Cipher - # This class is only provided for backwards compatibility. Use OpenSSL::Cipher in the future. - class Cipher < Cipher - # add warning - end - end # Cipher -end # OpenSSL \ No newline at end of file diff --git a/lib/jopenssl22/openssl/config.rb b/lib/jopenssl22/openssl/config.rb deleted file mode 100644 index 24a54c91..00000000 --- a/lib/jopenssl22/openssl/config.rb +++ /dev/null @@ -1,313 +0,0 @@ -=begin -= Ruby-space definitions that completes C-space funcs for Config - -= Info - Copyright (C) 2010 Hiroshi Nakamura - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -=end - -require 'stringio' - -module OpenSSL - class Config - include Enumerable - - class << self - def parse(str) - c = new() - parse_config(StringIO.new(str)).each do |section, hash| - c[section] = hash - end - c - end - - alias load new - - def parse_config(io) - begin - parse_config_lines(io) - rescue ConfigError => e - e.message.replace("error in line #{io.lineno}: " + e.message) - raise - end - end - - def get_key_string(data, section, key) # :nodoc: - if v = data[section] && data[section][key] - return v - elsif section == 'ENV' - if v = ENV[key] - return v - end - end - if v = data['default'] && data['default'][key] - return v - end - end - - private - - def parse_config_lines(io) - section = 'default' - data = {section => {}} - while definition = get_definition(io) - definition = clear_comments(definition) - next if definition.empty? - if definition[0] == ?[ - if /\[([^\]]*)\]/ =~ definition - section = $1.strip - data[section] ||= {} - else - raise ConfigError, "missing close square bracket" - end - else - if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition - if $2 - section = $1 - key = $2 - else - key = $1 - end - value = unescape_value(data, section, $3) - (data[section] ||= {})[key] = value.strip - else - raise ConfigError, "missing equal sign" - end - end - end - data - end - - # escape with backslash - QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ - # escape with backslash and doubled dq - QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ - # escaped char map - ESCAPE_MAP = { - "r" => "\r", - "n" => "\n", - "b" => "\b", - "t" => "\t", - } - - def unescape_value(data, section, value) - scanned = [] - while m = value.match(/['"\\$]/) - scanned << m.pre_match - c = m[0] - value = m.post_match - case c - when "'" - if m = value.match(QUOTE_REGEXP_SQ) - scanned << m[1].gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when '"' - if m = value.match(QUOTE_REGEXP_DQ) - scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when "\\" - c = value.slice!(0, 1) - scanned << (ESCAPE_MAP[c] || c) - when "$" - ref, value = extract_reference(value) - refsec = section - if ref.index('::') - refsec, ref = ref.split('::', 2) - end - if v = get_key_string(data, refsec, ref) - scanned << v - else - raise ConfigError, "variable has no value" - end - else - raise 'must not reaced' - end - end - scanned << value - scanned.join - end - - def extract_reference(value) - rest = '' - if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) - value = m[1] || m[2] - rest = m.post_match - elsif [?(, ?{].include?(value[0]) - raise ConfigError, "no close brace" - end - if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) - return m[0], m.post_match + rest - else - raise - end - end - - def clear_comments(line) - # FCOMMENT - if m = line.match(/\A([\t\n\f ]*);.*\z/) - return m[1] - end - # COMMENT - scanned = [] - while m = line.match(/[#'"\\]/) - scanned << m.pre_match - c = m[0] - line = m.post_match - case c - when '#' - line = nil - break - when "'", '"' - regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ - scanned << c - if m = line.match(regexp) - scanned << m[0] - line = m.post_match - else - scanned << line - line = nil - break - end - when "\\" - scanned << c - scanned << line.slice!(0, 1) - else - raise 'must not reaced' - end - end - scanned << line - scanned.join - end - - def get_definition(io) - if line = get_line(io) - while /[^\\]\\\z/ =~ line - if extra = get_line(io) - line += extra - else - break - end - end - return line.strip - end - end - - def get_line(io) - if line = io.gets - line.gsub(/[\r\n]*/, '') - end - end - end - - def initialize(filename = nil) - @data = {} - if filename - File.open(filename.to_s) do |file| - Config.parse_config(file).each do |section, hash| - self[section] = hash - end - end - end - end - - def get_value(section, key) - if section.nil? - raise TypeError.new('nil not allowed') - end - section = 'default' if section.empty? - get_key_string(section, key) - end - - def value(arg1, arg2 = nil) - warn('Config#value is deprecated; use Config#get_value') - if arg2.nil? - section, key = 'default', arg1 - else - section, key = arg1, arg2 - end - section ||= 'default' - section = 'default' if section.empty? - get_key_string(section, key) - end - - def add_value(section, key, value) - check_modify - (@data[section] ||= {})[key] = value - end - - def [](section) - @data[section] || {} - end - - def section(name) - warn('Config#section is deprecated; use Config#[]') - @data[name] || {} - end - - def []=(section, pairs) - check_modify - @data[section] ||= {} - pairs.each do |key, value| - self.add_value(section, key, value) - end - end - - def sections - @data.keys - end - - def to_s - ary = [] - @data.keys.sort.each do |section| - ary << "[ #{section} ]\n" - @data[section].keys.each do |key| - ary << "#{key}=#{@data[section][key]}\n" - end - ary << "\n" - end - ary.join - end - - def each - @data.each do |section, hash| - hash.each do |key, value| - yield [section, key, value] - end - end - end - - def inspect - "#<#{self.class.name} sections=#{sections.inspect}>" - end - - protected - - def data - @data - end - - private - - def initialize_copy(other) - @data = other.data.dup - end - - def check_modify - raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? - end - - def get_key_string(section, key) - Config.get_key_string(@data, section, key) - end - end -end diff --git a/lib/jopenssl22/openssl/digest.rb b/lib/jopenssl22/openssl/digest.rb deleted file mode 100644 index c1d8bce6..00000000 --- a/lib/jopenssl22/openssl/digest.rb +++ /dev/null @@ -1,54 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = Ruby-space predefined Digest subclasses -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - class Digest - # Deprecated. - # - # This class is only provided for backwards compatibility. - class Digest < Digest # :nodoc: - # Deprecated. - # - # See OpenSSL::Digest.new - def initialize(*args) - warn('Digest::Digest is deprecated; use Digest') - super(*args) - end - end - - end # Digest - - # Returns a Digest subclass by +name+. - # - # require 'openssl' - # - # OpenSSL::Digest("MD5") - # # => OpenSSL::Digest::MD5 - # - # Digest("Foo") - # # => NameError: wrong constant name Foo - - def Digest(name) - OpenSSL::Digest.const_get(name) - end - - module_function :Digest - -end # OpenSSL diff --git a/lib/jopenssl22/openssl/ssl.rb b/lib/jopenssl22/openssl/ssl.rb deleted file mode 100644 index dc8707ec..00000000 --- a/lib/jopenssl22/openssl/ssl.rb +++ /dev/null @@ -1,330 +0,0 @@ -=begin -= $RCSfile$ -- Ruby-space definitions that completes C-space funcs for SSL - -= Info - 'OpenSSL for Ruby 2' project - Copyright (C) 2001 GOTOU YUUZOU - All rights reserved. - -= Licence - This program is licenced under the same licence as Ruby. - (See the file 'LICENCE'.) - -= Version - $Id$ -=end - -require "openssl/buffering" -require "fcntl" - -module OpenSSL - module SSL - class SSLContext - DEFAULT_PARAMS = { - :ssl_version => "SSLv23", - :verify_mode => OpenSSL::SSL::VERIFY_PEER, - :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 - ECDHE-ECDSA-RC4-SHA - ECDHE-RSA-RC4-SHA - RC4-SHA - }.join(":"), - :options => -> { - opts = OpenSSL::SSL::OP_ALL - opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) - opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) - opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) - opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) - opts - }.call - } unless const_defined? :DEFAULT_PARAMS # JRuby does it in Java - - begin - DEFAULT_CERT_STORE = OpenSSL::X509::Store.new - DEFAULT_CERT_STORE.set_default_paths - if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - end - end unless const_defined? :DEFAULT_CERT_STORE - - ## - # Sets the parameters for this SSL context to the values in +params+. - # The keys in +params+ must be assignment methods on SSLContext. - # - # If the verify_mode is not VERIFY_NONE and ca_file, ca_path and - # cert_store are not set then the system default certificate store is - # used. - - def set_params(params={}) - params = DEFAULT_PARAMS.merge(params) - params.each { |name, value| self.__send__("#{name}=", value) } - if self.verify_mode != OpenSSL::SSL::VERIFY_NONE - unless self.ca_file or self.ca_path or self.cert_store - self.cert_store = DEFAULT_CERT_STORE - end - end - return params - end unless method_defined? :set_params - end - - module SocketForwarder - def addr - to_io.addr - end - - def peeraddr - to_io.peeraddr - end - - def setsockopt(level, optname, optval) - to_io.setsockopt(level, optname, optval) - end - - def getsockopt(level, optname) - to_io.getsockopt(level, optname) - end - - def fcntl(*args) - to_io.fcntl(*args) - end - - def closed? - to_io.closed? - end - - def do_not_reverse_lookup=(flag) - to_io.do_not_reverse_lookup = flag - end - end - - module Nonblock - def initialize(*args) - flag = File::NONBLOCK - flag |= @io.fcntl(Fcntl::F_GETFL) if defined?(Fcntl::F_GETFL) - @io.fcntl(Fcntl::F_SETFL, flag) - super - end - end unless const_defined? :Nonblock # JRuby: hooked up in "native" Java - - def verify_certificate_identity(cert, hostname) - should_verify_common_name = true - cert.extensions.each { |ext| - next if ext.oid != "subjectAltName" - ext.value.split(/,\s+/).each { |general_name| - #case san.tag - # MRI 2.2.3 (JRuby parses ASN.1 differently) - #when 2 # dNSName in GeneralName (RFC5280) - if /\ADNS:(.*)/ =~ general_name - should_verify_common_name = false - return true if verify_hostname(hostname, $1) - # MRI 2.2.3 (JRuby parses ASN.1 differently) - #when 7 # iPAddress in GeneralName (RFC5280) - elsif /\AIP(?: Address)?:(.*)/ =~ general_name - should_verify_common_name = false - return true if $1 == hostname - # NOTE: bellow logic makes little sense JRuby reads exts differently - # follows GENERAL_NAME_print() in x509v3/v3_alt.c - #if san.value.size == 4 - # return true if san.value.unpack('C*').join('.') == hostname - #elsif san.value.size == 16 - # return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname - #end - end - } - } - if should_verify_common_name - cert.subject.to_a.each{|oid, value| - if oid == "CN" - return true if verify_hostname(hostname, value) - end - } - end - return false - end - module_function :verify_certificate_identity - - def verify_hostname(hostname, san) # :nodoc: - # RFC 5280, IA5String is limited to the set of ASCII characters - return false unless san.ascii_only? - return false unless hostname.ascii_only? - - # See RFC 6125, section 6.4.1 - # Matching is case-insensitive. - san_parts = san.downcase.split(".") - - # TODO: this behavior should probably be more strict - return san == hostname if san_parts.size < 2 - - # Matching is case-insensitive. - host_parts = hostname.downcase.split(".") - - # RFC 6125, section 6.4.3, subitem 2. - # If the wildcard character is the only character of the left-most - # label in the presented identifier, the client SHOULD NOT compare - # against anything but the left-most label of the reference - # identifier (e.g., *.example.com would match foo.example.com but - # not bar.foo.example.com or example.com). - return false unless san_parts.size == host_parts.size - - # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in - # which the wildcard character comprises a label other than the - # left-most label (e.g., do not match bar.*.example.net). - return false unless verify_wildcard(host_parts.shift, san_parts.shift) - - san_parts.join(".") == host_parts.join(".") - end - module_function :verify_hostname - - def verify_wildcard(domain_component, san_component) # :nodoc: - parts = san_component.split("*", -1) - - return false if parts.size > 2 - return san_component == domain_component if parts.size == 1 - - # RFC 6125, section 6.4.3, subitem 3. - # The client SHOULD NOT attempt to match a presented identifier - # where the wildcard character is embedded within an A-label or - # U-label of an internationalized domain name. - return false if domain_component.start_with?("xn--") && san_component != "*" - - parts[0].length + parts[1].length < domain_component.length && - domain_component.start_with?(parts[0]) && - domain_component.end_with?(parts[1]) - end - module_function :verify_wildcard - - class SSLSocket - include Buffering - include SocketForwarder - include Nonblock - - def sysclose - return if closed? - stop - io.close if sync_close - end unless method_defined? :sysclose - - ## - # Perform hostname verification after an SSL connection is established - # - # 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 - - private - - def using_anon_cipher? - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "aNULL" - ctx.ciphers.include?(cipher) - end - end - - ## - # SSLServer represents a TCP/IP server socket with Secure Sockets Layer. - class SSLServer - include SocketForwarder - # When true then #accept works exactly the same as TCPServer#accept - attr_accessor :start_immediately - - # Creates a new instance of SSLServer. - # * +srv+ is an instance of TCPServer. - # * +ctx+ is an instance of OpenSSL::SSL::SSLContext. - def initialize(svr, ctx) - @svr = svr - @ctx = ctx - unless ctx.session_id_context - # see #6137 - session id may not exceed 32 bytes - prng = ::Random.new($0.hash) - session_id = prng.bytes(16).unpack('H*')[0] - @ctx.session_id_context = session_id - end - @start_immediately = true - end - - # Returns the TCPServer passed to the SSLServer when initialized. - def to_io - @svr - end - - # See TCPServer#listen for details. - def listen(backlog=5) - @svr.listen(backlog) - end - - # See BasicSocket#shutdown for details. - def shutdown(how=Socket::SHUT_RDWR) - @svr.shutdown(how) - end - - # Works similar to TCPServer#accept. - def accept - # Socket#accept returns [socket, addrinfo]. - # TCPServer#accept returns a socket. - # The following comma strips addrinfo. - sock, = @svr.accept - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) - ssl.sync_close = true - ssl.accept if @start_immediately - ssl - rescue Exception => ex - if ssl - ssl.close - else - sock.close - end - raise ex - end - end - - # See IO#close for details. - def close - @svr.close - end - end - end -end diff --git a/lib/jopenssl22/openssl/x509.rb b/lib/jopenssl22/openssl/x509.rb deleted file mode 100644 index c8acf824..00000000 --- a/lib/jopenssl22/openssl/x509.rb +++ /dev/null @@ -1,139 +0,0 @@ -#-- -# -# $RCSfile$ -# -# = 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 licenced under the same licence as Ruby. -# (See the file 'LICENCE'.) -# -# = Version -# $Id$ -# -#++ - -module OpenSSL - module X509 - class Name - module RFC2253DN - Special = ',=+<>#;' - HexChar = /[0-9a-fA-F]/ - HexPair = /#{HexChar}#{HexChar}/ - HexString = /#{HexPair}+/ - Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/ - StringChar = /[^\\"#{Special}]/ - QuoteChar = /[^\\"]/ - AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/ - AttributeValue = / - (?!["#])((?:#{StringChar}|#{Pair})*)| - \#(#{HexString})| - "((?:#{QuoteChar}|#{Pair})*)" - /x - TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/ - - module_function - - def expand_pair(str) - return nil unless str - return str.gsub(Pair){ - pair = $& - case pair.size - when 2 then pair[1,1] - when 3 then Integer("0x#{pair[1,2]}").chr - else raise OpenSSL::X509::NameError, "invalid pair: #{str}" - end - } - end - - def expand_hexstring(str) - return nil unless str - der = str.gsub(HexPair){$&.to_i(16).chr } - a1 = OpenSSL::ASN1.decode(der) - return a1.value, a1.tag - end - - def expand_value(str1, str2, str3) - value = expand_pair(str1) - value, tag = expand_hexstring(str2) unless value - value = expand_pair(str3) unless value - return value, tag - end - - def scan(dn) - str = dn - ary = [] - while true - if md = TypeAndValue.match(str) - remain = md.post_match - type = md[1] - value, tag = expand_value(md[2], md[3], md[4]) rescue nil - if value - type_and_value = [type, value] - type_and_value.push(tag) if tag - ary.unshift(type_and_value) - if remain.length > 2 && remain[0] == ?, - str = remain[1..-1] - next - elsif remain.length > 2 && remain[0] == ?+ - raise OpenSSL::X509::NameError, - "multi-valued RDN is not supported: #{dn}" - elsif remain.empty? - break - end - end - end - msg_dn = dn[0, dn.length - str.length] + " =>" + str - raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}" - end - return ary - end - end - - class << self - def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) - ary = OpenSSL::X509::Name::RFC2253DN.scan(str) - self.new(ary, template) - end - - def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) - ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } - self.new(ary, template) - end - - alias parse parse_openssl - end - - def pretty_print(q) - q.object_group(self) { - q.text ' ' - q.text to_s(OpenSSL::X509::Name::RFC2253) - } - end - end - - class StoreContext - def cleanup - warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE - end - end - - class Certificate - def pretty_print(q) - q.object_group(self) { - q.breakable - q.text 'subject='; q.pp self.subject; q.text ','; q.breakable - q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable - q.text 'serial='; q.pp self.serial; q.text ','; q.breakable - q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable - q.text 'not_after='; q.pp self.not_after - } - end - end - end -end diff --git a/lib/jopenssl23/openssl.rb b/lib/jopenssl23/openssl.rb deleted file mode 100644 index 31502184..00000000 --- a/lib/jopenssl23/openssl.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: false -=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 'LICENCE'.) -=end - -require 'openssl/bn' -require 'openssl/pkey' -require 'openssl/cipher' -require 'openssl/config' if OpenSSL.const_defined?(:Config, false) -require 'openssl/digest' -require 'openssl/x509' -require 'openssl/ssl' diff --git a/lib/jopenssl23/openssl/bn.rb b/lib/jopenssl23/openssl/bn.rb deleted file mode 100644 index b900a18c..00000000 --- a/lib/jopenssl23/openssl/bn.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: false -#-- -# -# = Ruby-space definitions that completes C-space funcs for BN -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -#++ - -module OpenSSL - class BN - def pretty_print(q) - q.object_group(self) { - q.text ' ' - q.text to_i.to_s - } - end - end # BN -end # OpenSSL - -## -#-- -# Add double dispatch to Integer -#++ -class Integer - # Casts an Integer as an OpenSSL::BN - # - # See `man bn` for more info. - def to_bn - OpenSSL::BN::new(self) - end -end # Integer diff --git a/lib/jopenssl23/openssl/buffering.rb b/lib/jopenssl23/openssl/buffering.rb deleted file mode 100644 index 07ad7cf2..00000000 --- a/lib/jopenssl23/openssl/buffering.rb +++ /dev/null @@ -1,455 +0,0 @@ -# coding: binary -# frozen_string_literal: false -#-- -#= 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 'LICENCE'.) -#++ - -## -# OpenSSL IO buffering mix-in module. -# -# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO. -# -# You typically won't use this module directly, you can see it implemented in -# OpenSSL::SSL::SSLSocket. - -module OpenSSL::Buffering - include Enumerable - - ## - # The "sync mode" of the SSLSocket. - # - # See IO#sync for full details. - - attr_accessor :sync - - ## - # Default size to read from or write to the SSLSocket for buffer operations. - - BLOCK_SIZE = 1024*16 - - ## - # Creates an instance of OpenSSL's buffering IO module. - - def initialize(*) - # super - @eof = false - @rbuffer = "" - @sync = @io.sync - end - - # - # for reading. - # - private - - ## - # Fills the buffer from the underlying SSLSocket - - def fill_rbuff - begin - @rbuffer << self.sysread(BLOCK_SIZE) - rescue Errno::EAGAIN - retry - rescue EOFError - @eof = true - end - end - - ## - # Consumes _size_ bytes from the buffer - - def consume_rbuff(size=nil) - if @rbuffer.empty? - nil - else - size = @rbuffer.size unless size - ret = @rbuffer[0, size] - @rbuffer[0, size] = "" - ret - end - end - - public - - ## - # Reads _size_ bytes from the stream. If _buf_ is provided it must - # reference a string which will receive the data. - # - # See IO#read for full details. - - def read(size=nil, buf=nil) - if size == 0 - if buf - buf.clear - return buf - else - return "" - end - end - until @eof - break if size && size <= @rbuffer.size - fill_rbuff - end - ret = consume_rbuff(size) || "" - if buf - buf.replace(ret) - ret = buf - end - (size && ret.empty?) ? nil : ret - end - - ## - # Reads at most _maxlen_ bytes from the stream. If _buf_ is provided it - # must reference a string which will receive the data. - # - # See IO#readpartial for full details. - - def readpartial(maxlen, buf=nil) - if maxlen == 0 - if buf - buf.clear - return buf - else - return "" - end - end - if @rbuffer.empty? - begin - return sysread(maxlen, buf) - rescue Errno::EAGAIN - retry - end - end - ret = consume_rbuff(maxlen) - if buf - buf.replace(ret) - ret = buf - end - ret - end - - ## - # Reads at most _maxlen_ bytes in the non-blocking manner. - # - # When no data can be read without blocking it raises - # OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable. - # - # IO::WaitReadable means SSL needs to read internally so read_nonblock - # should be called again when the underlying IO is readable. - # - # IO::WaitWritable means SSL needs to write internally so read_nonblock - # should be called again after the underlying IO is writable. - # - # OpenSSL::Buffering#read_nonblock needs two rescue clause as follows: - # - # # emulates blocking read (readpartial). - # begin - # result = ssl.read_nonblock(maxlen) - # rescue IO::WaitReadable - # IO.select([io]) - # retry - # rescue IO::WaitWritable - # IO.select(nil, [io]) - # retry - # end - # - # Note that one reason that read_nonblock writes to the underlying IO is - # when the peer requests a new TLS/SSL handshake. See openssl the FAQ for - # more details. http://www.openssl.org/support/faq.html - # - # By specifying 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) - idx = @rbuffer.index(eol) - until @eof - break if idx - fill_rbuff - idx = @rbuffer.index(eol) - end - if eol.is_a?(Regexp) - size = idx ? idx+$&.size : nil - else - size = idx ? idx+eol.size : nil - end - if size && limit && limit >= 0 - size = [size, limit].min - end - consume_rbuff(size) - end - - ## - # Executes the block for every line in the stream where lines are separated - # by _eol_. - # - # See also #gets - - def each(eol=$/) - while line = self.gets(eol) - yield line - end - end - alias each_line each - - ## - # Reads lines from the stream which are separated by _eol_. - # - # See also #gets - - def readlines(eol=$/) - ary = [] - while line = self.gets(eol) - ary << line - end - ary - end - - ## - # Reads a line from the stream which is separated by _eol_. - # - # Raises EOFError if at end of file. - - def readline(eol=$/) - raise EOFError if eof? - gets(eol) - end - - ## - # Reads one character from the stream. Returns nil if called at end of - # file. - - def getc - read(1) - end - - ## - # Calls the given block once for each byte in the stream. - - def each_byte # :yields: byte - while c = getc - yield(c.ord) - end - end - - ## - # Reads a one-character string from the stream. Raises an EOFError at end - # of file. - - def readchar - raise EOFError if eof? - getc - end - - ## - # Pushes character _c_ back onto the stream such that a subsequent buffered - # character read will return it. - # - # Unlike IO#getc multiple bytes may be pushed back onto the stream. - # - # Has no effect on unbuffered reads (such as #sysread). - - def ungetc(c) - @rbuffer[0,0] = c.chr - end - - ## - # Returns true if the stream is at file which means there is no more data to - # be read. - - def eof? - fill_rbuff if !@eof && @rbuffer.empty? - @eof && @rbuffer.empty? - end - alias eof eof? - - # - # for writing. - # - private - - ## - # Writes _s_ to the buffer. When the buffer is full or #sync is true the - # buffer is flushed to the underlying socket. - - def do_write(s) - @wbuffer = "" unless defined? @wbuffer - @wbuffer << s - @wbuffer.force_encoding(Encoding::BINARY) - @sync ||= false - if @sync or @wbuffer.size > BLOCK_SIZE - until @wbuffer.empty? - begin - nwrote = syswrite(@wbuffer) - rescue Errno::EAGAIN - retry - end - @wbuffer[0, nwrote] = "" - 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 = "" - if args.empty? - s << "\n" - end - args.each{|arg| - s << arg.to_s - s.sub!(/(? -# All rights reserved. -# -# = Licence -# This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -#++ - -module OpenSSL - class Cipher - - # Deprecated. - # - # This class is only provided for backwards compatibility. - # Use OpenSSL::Cipher. - class Cipher < Cipher; end - deprecate_constant :Cipher - end # Cipher -end # OpenSSL diff --git a/lib/jopenssl23/openssl/config.rb b/lib/jopenssl23/openssl/config.rb deleted file mode 100644 index 48d8be00..00000000 --- a/lib/jopenssl23/openssl/config.rb +++ /dev/null @@ -1,474 +0,0 @@ -# frozen_string_literal: false -=begin -= Ruby-space definitions that completes C-space funcs for Config - -= Info - Copyright (C) 2010 Hiroshi Nakamura - -= Licence - This program is licensed under the same licence as Ruby. - (See the file 'LICENCE'.) - -=end - -require 'stringio' - -module OpenSSL - ## - # = OpenSSL::Config - # - # Configuration for the openssl library. - # - # Many system's installation of openssl library will depend on your system - # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for - # the location of the file for your host. - # - # See also http://www.openssl.org/docs/apps/config.html - class Config - include Enumerable - - class << self - - ## - # Parses a given _string_ as a blob that contains configuration for - # OpenSSL. - # - # If the source of the IO is a file, then consider using #parse_config. - def parse(string) - c = new() - parse_config(StringIO.new(string)).each do |section, hash| - c[section] = hash - end - c - end - - ## - # load is an alias to ::new - alias load new - - ## - # Parses the configuration data read from _io_, see also #parse. - # - # Raises a ConfigError on invalid configuration data. - def parse_config(io) - begin - parse_config_lines(io) - rescue ConfigError => e - e.message.replace("error in line #{io.lineno}: " + e.message) - raise - end - end - - def get_key_string(data, section, key) # :nodoc: - if v = data[section] && data[section][key] - return v - elsif section == 'ENV' - if v = ENV[key] - return v - end - end - if v = data['default'] && data['default'][key] - return v - end - end - - private - - def parse_config_lines(io) - section = 'default' - data = {section => {}} - while definition = get_definition(io) - definition = clear_comments(definition) - next if definition.empty? - if definition[0] == ?[ - if /\[([^\]]*)\]/ =~ definition - section = $1.strip - data[section] ||= {} - else - raise ConfigError, "missing close square bracket" - end - else - if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition - if $2 - section = $1 - key = $2 - else - key = $1 - end - value = unescape_value(data, section, $3) - (data[section] ||= {})[key] = value.strip - else - raise ConfigError, "missing equal sign" - end - end - end - data - end - - # escape with backslash - QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ - # escape with backslash and doubled dq - QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ - # escaped char map - ESCAPE_MAP = { - "r" => "\r", - "n" => "\n", - "b" => "\b", - "t" => "\t", - } - - def unescape_value(data, section, value) - scanned = [] - while m = value.match(/['"\\$]/) - scanned << m.pre_match - c = m[0] - value = m.post_match - case c - when "'" - if m = value.match(QUOTE_REGEXP_SQ) - scanned << m[1].gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when '"' - if m = value.match(QUOTE_REGEXP_DQ) - scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') - value = m.post_match - else - break - end - when "\\" - c = value.slice!(0, 1) - scanned << (ESCAPE_MAP[c] || c) - when "$" - ref, value = extract_reference(value) - refsec = section - if ref.index('::') - refsec, ref = ref.split('::', 2) - end - if v = get_key_string(data, refsec, ref) - scanned << v - else - raise ConfigError, "variable has no value" - end - else - raise 'must not reaced' - end - end - scanned << value - scanned.join - end - - def extract_reference(value) - rest = '' - if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) - value = m[1] || m[2] - rest = m.post_match - elsif [?(, ?{].include?(value[0]) - raise ConfigError, "no close brace" - end - if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) - return m[0], m.post_match + rest - else - raise - end - end - - def clear_comments(line) - # FCOMMENT - if m = line.match(/\A([\t\n\f ]*);.*\z/) - return m[1] - end - # COMMENT - scanned = [] - while m = line.match(/[#'"\\]/) - scanned << m.pre_match - c = m[0] - line = m.post_match - case c - when '#' - line = nil - break - when "'", '"' - regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ - scanned << c - if m = line.match(regexp) - scanned << m[0] - line = m.post_match - else - scanned << line - line = nil - break - end - when "\\" - scanned << c - scanned << line.slice!(0, 1) - else - raise 'must not reaced' - end - end - scanned << line - scanned.join - end - - def get_definition(io) - if line = get_line(io) - while /[^\\]\\\z/ =~ line - if extra = get_line(io) - line += extra - else - break - end - end - return line.strip - end - end - - def get_line(io) - if line = io.gets - line.gsub(/[\r\n]*/, '') - end - end - end - - ## - # Creates an instance of OpenSSL's configuration class. - # - # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config= - # - # If the optional _filename_ parameter is provided, then it is read in and - # parsed via #parse_config. - # - # This can raise IO exceptions based on the access, or availability of the - # file. A ConfigError exception may be raised depending on the validity of - # the data being configured. - # - def initialize(filename = nil) - @data = {} - if filename - File.open(filename.to_s) do |file| - Config.parse_config(file).each do |section, hash| - self[section] = hash - end - end - end - end - - ## - # Gets the value of _key_ from the given _section_ - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a specific value from the config if you know the _section_ - # and _key_ like so: - # - # config.get_value('default','foo') - # #=> "bar" - # - def get_value(section, key) - if section.nil? - raise TypeError.new('nil not allowed') - end - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # - # *Deprecated* - # - # Use #get_value instead - def value(arg1, arg2 = nil) # :nodoc: - warn('Config#value is deprecated; use Config#get_value') - if arg2.nil? - section, key = 'default', arg1 - else - section, key = arg1, arg2 - end - section ||= 'default' - section = 'default' if section.empty? - get_key_string(section, key) - end - - ## - # Set the target _key_ with a given _value_ under a specific _section_. - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can set the value of _foo_ under the _default_ section to a new - # value: - # - # config.add_value('default', 'foo', 'buzz') - # #=> "buzz" - # puts config.to_s - # #=> [ default ] - # # foo=buzz - # - def add_value(section, key, value) - check_modify - (@data[section] ||= {})[key] = value - end - - ## - # Get a specific _section_ from the current configuration - # - # Given the following configurating file being loaded: - # - # config = OpenSSL::Config.load('foo.cnf') - # #=> # - # puts config.to_s - # #=> [ default ] - # # foo=bar - # - # You can get a hash of the specific section like so: - # - # config['default'] - # #=> {"foo"=>"bar"} - # - def [](section) - @data[section] || {} - end - - ## - # Deprecated - # - # Use #[] instead - def section(name) # :nodoc: - warn('Config#section is deprecated; use Config#[]') - @data[name] || {} - end - - ## - # Sets a specific _section_ name with a Hash _pairs_. - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> # - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # It's important to note that this will essentially merge any of the keys - # in _pairs_ with the existing _section_. For example: - # - # config['default'] - # #=> {"foo"=>"bar", "baz"=>"buz"} - # config['default'] = {"foo" => "changed"} - # #=> {"foo"=>"changed"} - # config['default'] - # #=> {"foo"=>"changed", "baz"=>"buz"} - # - def []=(section, pairs) - check_modify - @data[section] ||= {} - pairs.each do |key, value| - self.add_value(section, key, value) - end - end - - ## - # Get the names of all sections in the current configuration - def sections - @data.keys - end - - ## - # Get the parsable form of the current configuration - # - # Given the following configuration being created: - # - # config = OpenSSL::Config.new - # #=> # - # config['default'] = {"foo"=>"bar","baz"=>"buz"} - # #=> {"foo"=>"bar", "baz"=>"buz"} - # puts config.to_s - # #=> [ default ] - # # foo=bar - # # baz=buz - # - # You can parse get the serialized configuration using #to_s and then parse - # it later: - # - # serialized_config = config.to_s - # # much later... - # new_config = OpenSSL::Config.parse(serialized_config) - # #=> # - # puts new_config - # #=> [ default ] - # foo=bar - # baz=buz - # - def to_s - ary = [] - @data.keys.sort.each do |section| - ary << "[ #{section} ]\n" - @data[section].keys.each do |key| - ary << "#{key}=#{@data[section][key]}\n" - end - ary << "\n" - end - ary.join - end - - ## - # For a block. - # - # Receive the section and its pairs for the current configuration. - # - # config.each do |section, key, value| - # # ... - # end - # - def each - @data.each do |section, hash| - hash.each do |key, value| - yield [section, key, value] - end - end - end - - ## - # String representation of this configuration object, including the class - # name and its sections. - def inspect - "#<#{self.class.name} sections=#{sections.inspect}>" - end - - protected - - def data # :nodoc: - @data - end - - private - - def initialize_copy(other) - @data = other.data.dup - end - - def check_modify - raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? - end - - def get_key_string(section, key) - Config.get_key_string(@data, section, key) - end - end -end diff --git a/lib/jopenssl23/openssl/digest.rb b/lib/jopenssl23/openssl/digest.rb deleted file mode 100644 index c087bc0f..00000000 --- a/lib/jopenssl23/openssl/digest.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: false -#-- -# = 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 'LICENCE'.) -#++ - -module OpenSSL - class Digest - - # Deprecated. - # - # This class is only provided for backwards compatibility. - # Use OpenSSL::Digest instead. - class Digest < Digest; end # :nodoc: - deprecate_constant :Digest - - end # Digest - - # Returns a Digest subclass by _name_ - # - # require 'openssl' - # - # OpenSSL::Digest("MD5") - # # => OpenSSL::Digest::MD5 - # - # Digest("Foo") - # # => NameError: wrong constant name Foo - - def Digest(name) - OpenSSL::Digest.const_get(name) - end - - module_function :Digest - -end # OpenSSL diff --git a/lib/jopenssl23/openssl/pkey.rb b/lib/jopenssl23/openssl/pkey.rb deleted file mode 100644 index a12ba8b6..00000000 --- a/lib/jopenssl23/openssl/pkey.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: false -#-- -# Ruby/OpenSSL Project -# Copyright (C) 2017 Ruby/OpenSSL Project Authors -#++ - -module OpenSSL::PKey - if defined?(EC) - 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 -end diff --git a/lib/jopenssl23/openssl/ssl.rb b/lib/jopenssl23/openssl/ssl.rb deleted file mode 100644 index fbd04081..00000000 --- a/lib/jopenssl23/openssl/ssl.rb +++ /dev/null @@ -1,508 +0,0 @@ -# frozen_string_literal: false -=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 'LICENCE'.) -=end - -require "openssl/buffering" -require "io/nonblock" - -module OpenSSL - module SSL - class SSLContext - unless const_defined? :DEFAULT_PARAMS # JRuby does it in Java - DEFAULT_PARAMS = { # :nodoc: - :min_version => OpenSSL::SSL::TLS1_VERSION, - :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") && - OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000) - DEFAULT_PARAMS.merge!( - ciphers: %w{ - ECDHE-ECDSA-AES128-GCM-SHA256 - ECDHE-RSA-AES128-GCM-SHA256 - ECDHE-ECDSA-AES256-GCM-SHA384 - ECDHE-RSA-AES256-GCM-SHA384 - DHE-RSA-AES128-GCM-SHA256 - DHE-DSS-AES128-GCM-SHA256 - DHE-RSA-AES256-GCM-SHA384 - DHE-DSS-AES256-GCM-SHA384 - ECDHE-ECDSA-AES128-SHA256 - ECDHE-RSA-AES128-SHA256 - ECDHE-ECDSA-AES128-SHA - ECDHE-RSA-AES128-SHA - ECDHE-ECDSA-AES256-SHA384 - ECDHE-RSA-AES256-SHA384 - ECDHE-ECDSA-AES256-SHA - ECDHE-RSA-AES256-SHA - DHE-RSA-AES128-SHA256 - DHE-RSA-AES256-SHA256 - DHE-RSA-AES128-SHA - DHE-RSA-AES256-SHA - DHE-DSS-AES128-SHA256 - DHE-DSS-AES256-SHA256 - DHE-DSS-AES128-SHA - DHE-DSS-AES256-SHA - AES128-GCM-SHA256 - AES256-GCM-SHA384 - AES128-SHA256 - AES256-SHA256 - AES128-SHA - AES256-SHA - }.join(":"), - ) - end - end - - if defined?(OpenSSL::PKey::DH) - DEFAULT_2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY -JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab -VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 -YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 -1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD -7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== ------END DH PARAMETERS----- - _end_of_pem_ - private_constant :DEFAULT_2048 - - DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: - warn "using default DH parameters." if $VERBOSE - DEFAULT_2048 - } - end - - begin - DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: - DEFAULT_CERT_STORE.set_default_paths - DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - end unless const_defined? :DEFAULT_CERT_STORE # JRuby - - # A callback invoked when DH parameters are required. - # - # The callback is invoked with the Session for the key exchange, an - # flag indicating the use of an export cipher and the keylength - # required. - # - # The callback must return an OpenSSL::PKey::DH instance of the correct - # key length. - - attr_accessor :tmp_dh_callback - - # A callback invoked at connect time to distinguish between multiple - # server names. - # - # The callback is invoked with an SSLSocket and a server name. The - # callback must return an SSLContext for the server name or nil. - attr_accessor :servername_cb - - # 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.options |= OpenSSL::SSL::OP_ALL - # self.ssl_version = version if version - # end - - ## - # call-seq: - # ctx.set_params(params = {}) -> params - # - # Sets saner defaults optimized for the use with HTTP-like protocols. - # - # If a Hash _params_ is given, the parameters are overridden with it. - # The keys in _params_ must be assignment methods on SSLContext. - # - # If the verify_mode is not VERIFY_NONE and ca_file, ca_path and - # cert_store are not set then the system default certificate store is - # used. - def set_params(params={}) - params = DEFAULT_PARAMS.merge(params) - # TODO JRuby: need to support SSLContext#options (since Ruby 2.5) - #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 - self.cert_store = DEFAULT_CERT_STORE - end - end - return params - end unless method_defined? :set_params - - # 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". - # - # Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v* - # options by #options= once you have called #min_version= or - # #max_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 - def min_version=(version) - set_minmax_proto_version(version, @max_proto_version ||= nil) - @min_proto_version = version - end - - # 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. - def max_version=(version) - set_minmax_proto_version(@min_proto_version ||= nil, version) - @max_proto_version = version - 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 - set_minmax_proto_version(version, version) - @min_proto_version = @max_proto_version = version - end unless method_defined? :ssl_version= - - 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 - - # METHODS setup from native (JRuby) - # deprecate_constant :METHODS - end - - module SocketForwarder - def addr - to_io.addr - end - - def peeraddr - to_io.peeraddr - end - - def setsockopt(level, optname, optval) - to_io.setsockopt(level, optname, optval) - end - - def getsockopt(level, optname) - to_io.getsockopt(level, optname) - end - - def fcntl(*args) - to_io.fcntl(*args) - end - - def closed? - to_io.closed? - end - - def do_not_reverse_lookup=(flag) - to_io.do_not_reverse_lookup = flag - end - end unless const_defined? :SocketForwarder # JRuby: hooked up in "native" Java - - def verify_certificate_identity(cert, hostname) - should_verify_common_name = true - cert.extensions.each{|ext| - next if ext.oid != "subjectAltName" - ext.value.split(/,\s+/).each { |general_name| - #case san.tag - # MRI 2.2.3 (JRuby parses ASN.1 differently) - #when 2 # dNSName in GeneralName (RFC5280) - if /\ADNS:(.*)/ =~ general_name - should_verify_common_name = false - return true if verify_hostname(hostname, $1) - # MRI 2.2.3 (JRuby parses ASN.1 differently) - #when 7 # iPAddress in GeneralName (RFC5280) - elsif /\AIP(?: Address)?:(.*)/ =~ general_name - should_verify_common_name = false - return true if $1 == hostname - # NOTE: bellow logic makes little sense JRuby reads exts differently - # follows GENERAL_NAME_print() in x509v3/v3_alt.c - #if san.value.size == 4 - # return true if san.value.unpack('C*').join('.') == hostname - #elsif san.value.size == 16 - # return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname - #end - end - } - } - if should_verify_common_name - cert.subject.to_a.each{|oid, value| - if oid == "CN" - return true if verify_hostname(hostname, value) - end - } - end - return false - end - module_function :verify_certificate_identity - - def verify_hostname(hostname, san) # :nodoc: - # RFC 5280, IA5String is limited to the set of ASCII characters - return false unless san.ascii_only? - return false unless hostname.ascii_only? - - # See RFC 6125, section 6.4.1 - # Matching is case-insensitive. - san_parts = san.downcase.split(".") - - # TODO: this behavior should probably be more strict - return san == hostname if san_parts.size < 2 - - # Matching is case-insensitive. - host_parts = hostname.downcase.split(".") - - # RFC 6125, section 6.4.3, subitem 2. - # If the wildcard character is the only character of the left-most - # label in the presented identifier, the client SHOULD NOT compare - # against anything but the left-most label of the reference - # identifier (e.g., *.example.com would match foo.example.com but - # not bar.foo.example.com or example.com). - return false unless san_parts.size == host_parts.size - - # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in - # which the wildcard character comprises a label other than the - # left-most label (e.g., do not match bar.*.example.net). - return false unless verify_wildcard(host_parts.shift, san_parts.shift) - - san_parts.join(".") == host_parts.join(".") - end - module_function :verify_hostname - - def verify_wildcard(domain_component, san_component) # :nodoc: - parts = san_component.split("*", -1) - - return false if parts.size > 2 - return san_component == domain_component if parts.size == 1 - - # RFC 6125, section 6.4.3, subitem 3. - # The client SHOULD NOT attempt to match a presented identifier - # where the wildcard character is embedded within an A-label or - # U-label of an internationalized domain name. - return false if domain_component.start_with?("xn--") && san_component != "*" - - parts[0].length + parts[1].length < domain_component.length && - domain_component.start_with?(parts[0]) && - domain_component.end_with?(parts[1]) - end - module_function :verify_wildcard - - class SSLSocket - include Buffering - include SocketForwarder - - # 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 unless method_defined? :sysclose - - # 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 unless method_defined? :session # JRuby - - private - - def using_anon_cipher? - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "aNULL" - ctx.ciphers.include?(cipher) - end - - def client_cert_cb - @context.client_cert_cb - end - - def tmp_dh_callback - @context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK - end - - def tmp_ecdh_callback - @context.tmp_ecdh_callback - end - - def session_new_cb - @context.session_new_cb - end - - def session_get_cb - @context.session_get_cb - end - end - - ## - # SSLServer represents a TCP/IP server socket with Secure Sockets Layer. - class SSLServer - include SocketForwarder - # When true then #accept works exactly the same as TCPServer#accept - attr_accessor :start_immediately - - # Creates a new instance of SSLServer. - # * _srv_ is an instance of TCPServer. - # * _ctx_ is an instance of OpenSSL::SSL::SSLContext. - def initialize(svr, ctx) - @svr = svr - @ctx = ctx - unless ctx.session_id_context - # see #6137 - session id may not exceed 32 bytes - prng = ::Random.new($0.hash) - session_id = prng.bytes(16).unpack('H*')[0] - @ctx.session_id_context = session_id - end - @start_immediately = true - end - - # Returns the TCPServer passed to the SSLServer when initialized. - def to_io - @svr - end - - # See TCPServer#listen for details. - def listen(backlog=5) - @svr.listen(backlog) - end - - # See BasicSocket#shutdown for details. - def shutdown(how=Socket::SHUT_RDWR) - @svr.shutdown(how) - end - - # Works similar to TCPServer#accept. - def accept - # Socket#accept returns [socket, addrinfo]. - # TCPServer#accept returns a socket. - # The following comma strips addrinfo. - sock, = @svr.accept - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) - ssl.sync_close = true - ssl.accept if @start_immediately - ssl - rescue Exception => ex - if ssl - ssl.close - else - sock.close - end - raise ex - end - end - - # See IO#close for details. - def close - @svr.close - end - end - end -end diff --git a/lib/jopenssl23/openssl/x509.rb b/lib/jopenssl23/openssl/x509.rb deleted file mode 100644 index 682b9627..00000000 --- a/lib/jopenssl23/openssl/x509.rb +++ /dev/null @@ -1,208 +0,0 @@ -# frozen_string_literal: false -#-- -# = Ruby-space definitions that completes C-space funcs for X509 and subclasses -# -# = Info -# 'OpenSSL for Ruby 2' project -# Copyright (C) 2002 Michal Rokos -# All rights reserved. -# -# = Licence -# This program is licensed under the same licence as Ruby. -# (See the file 'LICENCE'.) -#++ - -module OpenSSL - module X509 - # class ExtensionFactory - # def create_extension(*arg) - # if arg.size > 1 - # create_ext(*arg) - # else - # send("create_ext_from_"+arg[0].class.name.downcase, arg[0]) - # end - # end - # - # def create_ext_from_array(ary) - # raise ExtensionError, "unexpected array form" if ary.size > 3 - # create_ext(ary[0], ary[1], ary[2]) - # end - # - # def create_ext_from_string(str) # "oid = critical, value" - # oid, value = str.split(/=/, 2) - # oid.strip! - # value.strip! - # create_ext(oid, value) - # end - # - # def create_ext_from_hash(hash) - # create_ext(hash["oid"], hash["value"], hash["critical"]) - # end - # end - # - # class Extension - # def ==(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 - # end - - class Name - module RFC2253DN - Special = ',=+<>#;' - HexChar = /[0-9a-fA-F]/ - HexPair = /#{HexChar}#{HexChar}/ - HexString = /#{HexPair}+/ - Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/ - StringChar = /[^\\"#{Special}]/ - QuoteChar = /[^\\"]/ - AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/ - AttributeValue = / - (?!["#])((?:#{StringChar}|#{Pair})*)| - \#(#{HexString})| - "((?:#{QuoteChar}|#{Pair})*)" - /x - TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/ - - module_function - - def expand_pair(str) - return nil unless str - return str.gsub(Pair){ - pair = $& - case pair.size - when 2 then pair[1,1] - when 3 then Integer("0x#{pair[1,2]}").chr - else raise OpenSSL::X509::NameError, "invalid pair: #{str}" - end - } - end - - def expand_hexstring(str) - return nil unless str - der = str.gsub(HexPair){$&.to_i(16).chr } - a1 = OpenSSL::ASN1.decode(der) - return a1.value, a1.tag - end - - def expand_value(str1, str2, str3) - value = expand_pair(str1) - value, tag = expand_hexstring(str2) unless value - value = expand_pair(str3) unless value - return value, tag - end - - def scan(dn) - str = dn - ary = [] - while true - if md = TypeAndValue.match(str) - remain = md.post_match - type = md[1] - value, tag = expand_value(md[2], md[3], md[4]) rescue nil - if value - type_and_value = [type, value] - type_and_value.push(tag) if tag - ary.unshift(type_and_value) - if remain.length > 2 && remain[0] == ?, - str = remain[1..-1] - next - elsif remain.length > 2 && remain[0] == ?+ - raise OpenSSL::X509::NameError, - "multi-valued RDN is not supported: #{dn}" - elsif remain.empty? - break - end - end - end - msg_dn = dn[0, dn.length - str.length] + " =>" + str - raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}" - end - return ary - end - end - - class << self - def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE) - ary = OpenSSL::X509::Name::RFC2253DN.scan(str) - self.new(ary, template) - end - - def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) - 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 - # 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 - def pretty_print(q) - q.object_group(self) { - q.breakable - q.text 'subject='; q.pp self.subject; q.text ','; q.breakable - q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable - q.text 'serial='; q.pp self.serial; q.text ','; q.breakable - q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable - q.text 'not_after='; q.pp self.not_after - } - end - end - - # class CRL - # def ==(other) - # return false unless CRL === other - # to_der == other.to_der - # end - # end - - # class Request - # def ==(other) - # return false unless Request === other - # to_der == other.to_der - # end - # end - end -end diff --git a/lib/openssl.rb b/lib/openssl.rb index f3ce235d..e9f85bde 100644 --- a/lib/openssl.rb +++ b/lib/openssl.rb @@ -1 +1,3 @@ -require 'jopenssl/load' \ No newline at end of file +# frozen_string_literal: true + +require 'jopenssl/load' diff --git a/lib/openssl/bn.rb b/lib/openssl/bn.rb index a1b40772..0a5e11b4 100644 --- a/lib/openssl/bn.rb +++ b/lib/openssl/bn.rb @@ -1,9 +1,40 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# 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 'LICENCE'.) +#++ + +module OpenSSL + class BN + include Comparable + + def pretty_print(q) + q.object_group(self) { + q.text ' ' + q.text to_i.to_s + } + end + end # BN +end # OpenSSL + +## +#-- +# Add double dispatch to Integer +#++ +class Integer + # Casts an Integer as an OpenSSL::BN + # + # See `man bn` for more info. + def to_bn + OpenSSL::BN::new(self) + end +end # Integer diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index a1b40772..c0559abf 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -1,9 +1,480 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# 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 'LICENCE'.) +#++ + +## +# OpenSSL IO buffering mix-in module. +# +# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO. +# +# You typically won't use this module directly, you can see it implemented in +# OpenSSL::SSL::SSLSocket. + +module OpenSSL::Buffering + include Enumerable + + # A buffer which will retain binary encoding. + class Buffer < String + BINARY = Encoding::BINARY + + def initialize + super + + force_encoding(BINARY) + end + + def << string + if string.encoding == BINARY + super(string) + else + super(string.b) + end + + return self + end + + alias concat << + 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 << 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 + + ## + # 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) + # JRuby: sysread does `maxlen == 0` short-circuit check internally + if @rbuffer.empty? + begin + return sysread(maxlen, buf) + rescue Errno::EAGAIN + retry + end + end + do_consume_rbuff(maxlen, buf) + 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) + # JRuby: sysread does `maxlen == 0` short-circuit check internally + if @rbuffer.empty? + return sysread_nonblock(maxlen, buf, exception: exception) + end + do_consume_rbuff(maxlen, buf) + end + + # @private + def do_consume_rbuff(maxlen, buf) + if maxlen == 0 + if buf + buf.clear + return buf + else + return "" + end + end + + ret = consume_rbuff(maxlen) + if buf + buf.replace(ret) + ret = buf + end + ret + end + private :do_consume_rbuff + + ## + # Reads the next "line" from the stream. Lines are separated by _eol_. If + # _limit_ is provided the result will not be longer than the given number of + # bytes. + # + # _eol_ may be a String or Regexp. + # + # Unlike IO#gets the line read will not be assigned to +$_+. + # + # Unlike IO#gets the separator must be provided if a limit is provided. + + def gets(eol=$/, limit=nil) + idx = @rbuffer.index(eol) + until @eof + break if idx + fill_rbuff + idx = @rbuffer.index(eol) + end + if eol.is_a?(Regexp) + size = idx ? idx+$&.size : nil + else + size = idx ? idx+eol.size : nil + end + if size && limit && limit >= 0 + size = [size, limit].min + end + consume_rbuff(size) + end + + ## + # Executes the block for every line in the stream where lines are separated + # by _eol_. + # + # See also #gets + + def each(eol=$/) + while line = self.gets(eol) + yield line + end + end + alias each_line each + + ## + # Reads lines from the stream which are separated by _eol_. + # + # See also #gets + + def readlines(eol=$/) + ary = [] + while line = self.gets(eol) + ary << line + end + ary + end + + ## + # Reads a line from the stream which is separated by _eol_. + # + # Raises EOFError if at end of file. + + def readline(eol=$/) + raise EOFError if eof? + gets(eol) + end + + ## + # Reads one character from the stream. Returns nil if called at end of + # file. + + def getc + read(1) + end + + ## + # Calls the given block once for each byte in the stream. + + def each_byte # :yields: byte + while c = getc + yield(c.ord) + end + end + + ## + # Reads a one-character string from the stream. Raises an EOFError at end + # of file. + + def readchar + raise EOFError if eof? + getc + end + + ## + # Pushes character _c_ back onto the stream such that a subsequent buffered + # character read will return it. + # + # Unlike IO#getc multiple bytes may be pushed back onto the stream. + # + # Has no effect on unbuffered reads (such as #sysread). + + def ungetc(c) + @rbuffer[0,0] = c.chr + end + + ## + # Returns true if the stream is at file which means there is no more data to + # be read. + + def eof? + fill_rbuff if !@eof && @rbuffer.empty? + @eof && @rbuffer.empty? + end + alias eof eof? + + # + # for writing. + # + private + + ## + # Writes _s_ to the buffer. When the buffer is full or #sync is true the + # buffer is flushed to the underlying socket. + + def do_write(s) + @wbuffer ||= Buffer.new + @wbuffer << s + @sync ||= false + if @sync or @wbuffer.size > BLOCK_SIZE + until @wbuffer.empty? + begin + nwrote = syswrite(@wbuffer) + rescue Errno::EAGAIN + retry + end + @wbuffer[0, nwrote] = "" + 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 << "\n" + else + args.each do |arg| + s << arg.to_s + s.sub!(/(? '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# frozen_string_literal: true +#-- +# = Ruby-space predefined Cipher 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 'LICENCE'.) +#++ + +module OpenSSL + class Cipher + # %w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name| + # klass = Class.new(Cipher){ + # define_method(:initialize){|*args| + # cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" } + # super(cipher_name.downcase) + # } + # } + # const_set(name, klass) + # } + # + # %w(128 192 256).each{|keylen| + # klass = Class.new(Cipher){ + # define_method(:initialize){|mode = "CBC"| + # super("aes-#{keylen}-#{mode}".downcase) + # } + # } + # const_set("AES#{keylen}", klass) + # } + + # call-seq: + # cipher.random_key -> key + # + # Generate a random key with OpenSSL::Random.random_bytes and sets it to + # the cipher, and returns it. + # + # You must call #encrypt or #decrypt before calling this method. + # def random_key + # str = OpenSSL::Random.random_bytes(self.key_len) + # self.key = str + # end + + # call-seq: + # cipher.random_iv -> iv + # + # Generate a random IV with OpenSSL::Random.random_bytes and sets it to the + # cipher, and returns it. + # + # You must call #encrypt or #decrypt before calling this method. + # def random_iv + # str = OpenSSL::Random.random_bytes(self.iv_len) + # self.iv = str + # end + + # Deprecated. + # + # This class is only provided for backwards compatibility. + # Use OpenSSL::Cipher. + class Cipher < Cipher; end + deprecate_constant :Cipher + end # Cipher +end # OpenSSL diff --git a/lib/openssl/config.rb b/lib/openssl/config.rb index 20571d36..547b08f2 100644 --- a/lib/openssl/config.rb +++ b/lib/openssl/config.rb @@ -1,17 +1,504 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end +# frozen_string_literal: true +=begin += Ruby-space definitions that completes C-space funcs for Config + += Info + Copyright (C) 2010 Hiroshi Nakamura + += Licence + This program is licensed under the same licence as Ruby. + (See the file 'LICENCE'.) + +=end + +require 'stringio' -# @note moved from JOpenSSL native bits. module OpenSSL + class ConfigError < OpenSSLError; end + ## + # = OpenSSL::Config + # + # Configuration for the openssl library. + # + # Many system's installation of openssl library will depend on your system + # configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for + # the location of the file for your host. + # + # See also http://www.openssl.org/docs/apps/config.html class Config - DEFAULT_CONFIG_FILE = nil + include Enumerable + + DEFAULT_CONFIG_FILE = nil # JRuby: compatibility (we do not read openssl.cnf) + + class << self + + ## + # Parses a given _string_ as a blob that contains configuration for + # OpenSSL. + # + # If the source of the IO is a file, then consider using #parse_config. + def parse(string) + c = new() + parse_config(StringIO.new(string)).each do |section, hash| + c.set_section(section, hash) + end + c + end + + ## + # load is an alias to ::new + alias load new + + ## + # Parses the configuration data read from _io_, see also #parse. + # + # Raises a ConfigError on invalid configuration data. + def parse_config(io) + begin + parse_config_lines(io) + rescue => error + raise ConfigError, "error in line #{io.lineno}: " + error.message + end + end + + def get_key_string(data, section, key) # :nodoc: + if v = data[section] && data[section][key] + return v + elsif section == 'ENV' + if v = ENV[key] + return v + end + end + if v = data['default'] && data['default'][key] + return v + end + end + + private + + def parse_config_lines(io) + section = 'default' + data = {section => {}} + io_stack = [io] + while definition = get_definition(io_stack) + definition = clear_comments(definition) + next if definition.empty? + case definition + when /\A\[/ + if /\[([^\]]*)\]/ =~ definition + section = $1.strip + data[section] ||= {} + else + raise ConfigError, "missing close square bracket" + end + when /\A\.include (\s*=\s*)?(.+)\z/ + path = $2 + if File.directory?(path) + files = Dir.glob(File.join(path, "*.{cnf,conf}"), File::FNM_EXTGLOB) + else + files = [path] + end + + files.each do |filename| + begin + io_stack << StringIO.new(File.read(filename)) + rescue + raise ConfigError, "could not include file '%s'" % filename + end + end + when /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ + if $2 + section = $1 + key = $2 + else + key = $1 + end + value = unescape_value(data, section, $3) + (data[section] ||= {})[key] = value.strip + else + raise ConfigError, "missing equal sign" + end + end + data + end + + # escape with backslash + QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/ + # escape with backslash and doubled dq + QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/ + # escaped char map + ESCAPE_MAP = { + "r" => "\r", + "n" => "\n", + "b" => "\b", + "t" => "\t", + } + + def unescape_value(data, section, value) + scanned = [] + while m = value.match(/['"\\$]/) + scanned << m.pre_match + c = m[0] + value = m.post_match + case c + when "'" + if m = value.match(QUOTE_REGEXP_SQ) + scanned << m[1].gsub(/\\(.)/, '\\1') + value = m.post_match + else + break + end + when '"' + if m = value.match(QUOTE_REGEXP_DQ) + scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1') + value = m.post_match + else + break + end + when "\\" + c = value.slice!(0, 1) + scanned << (ESCAPE_MAP[c] || c) + when "$" + ref, value = extract_reference(value) + refsec = section + if ref.index('::') + refsec, ref = ref.split('::', 2) + end + if v = get_key_string(data, refsec, ref) + scanned << v + else + raise ConfigError, "variable has no value" + end + else + raise 'must not reaced' + end + end + scanned << value + scanned.join + end + + def extract_reference(value) + rest = '' + if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/) + value = m[1] || m[2] + rest = m.post_match + elsif [?(, ?{].include?(value[0]) + raise ConfigError, "no close brace" + end + if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/) + return m[0], m.post_match + rest + else + raise + end + end + + def clear_comments(line) + # FCOMMENT + if m = line.match(/\A([\t\n\f ]*);.*\z/) + return m[1] + end + # COMMENT + scanned = [] + while m = line.match(/[#'"\\]/) + scanned << m.pre_match + c = m[0] + line = m.post_match + case c + when '#' + line = nil + break + when "'", '"' + regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ + scanned << c + if m = line.match(regexp) + scanned << m[0] + line = m.post_match + else + scanned << line + line = nil + break + end + when "\\" + scanned << c + scanned << line.slice!(0, 1) + else + raise 'must not reaced' + end + end + scanned << line + scanned.join + end + + def get_definition(io_stack) + if line = get_line(io_stack) + while /[^\\]\\\z/ =~ line + if extra = get_line(io_stack) + line += extra + else + break + end + end + return line.strip + end + end + + def get_line(io_stack) + while io = io_stack.last + if line = io.gets + return line.gsub(/[\r\n]*/, '') + end + io_stack.pop + end + end + end + + ## + # Creates an instance of OpenSSL's configuration class. + # + # This can be used in contexts like OpenSSL::X509::ExtensionFactory.config= + # + # If the optional _filename_ parameter is provided, then it is read in and + # parsed via #parse_config. + # + # This can raise IO exceptions based on the access, or availability of the + # file. A ConfigError exception may be raised depending on the validity of + # the data being configured. + # + def initialize(filename = nil) + @data = {} + if filename + File.open(filename.to_s) do |file| + Config.parse_config(file).each do |section, hash| + set_section(section, hash) + end + end + end + end + + ## + # Gets the value of _key_ from the given _section_ + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> # + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can get a specific value from the config if you know the _section_ + # and _key_ like so: + # + # config.get_value('default','foo') + # #=> "bar" + # + def get_value(section, key) + if section.nil? + raise TypeError.new('nil not allowed') + end + section = 'default' if section.empty? + get_key_string(section, key) + end + + ## + # + # *Deprecated* + # + # Use #get_value instead + def value(arg1, arg2 = nil) # :nodoc: + warn('Config#value is deprecated; use Config#get_value') + if arg2.nil? + section, key = 'default', arg1 + else + section, key = arg1, arg2 + end + section ||= 'default' + section = 'default' if section.empty? + get_key_string(section, key) + end + + ## + # *Deprecated in v2.2.0*. This method will be removed in a future release. + # + # Set the target _key_ with a given _value_ under a specific _section_. + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> # + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can set the value of _foo_ under the _default_ section to a new + # value: + # + # config.add_value('default', 'foo', 'buzz') + # #=> "buzz" + # puts config.to_s + # #=> [ default ] + # # foo=buzz + # + def add_value(section, key, value) + check_modify + (@data[section] ||= {})[key] = value + end + + ## + # Get a specific _section_ from the current configuration + # + # Given the following configurating file being loaded: + # + # config = OpenSSL::Config.load('foo.cnf') + # #=> # + # puts config.to_s + # #=> [ default ] + # # foo=bar + # + # You can get a hash of the specific section like so: + # + # config['default'] + # #=> {"foo"=>"bar"} + # + def [](section) + @data[section] || {} + end + + ## + # Deprecated + # + # Use #[] instead + def section(name) # :nodoc: + warn('Config#section is deprecated; use Config#[]') + @data[name] || {} + end + + ## + # *Deprecated in v2.2.0*. This method will be removed in a future release. + # + # Sets a specific _section_ name with a Hash _pairs_. + # + # Given the following configuration being created: + # + # config = OpenSSL::Config.new + # #=> # + # config['default'] = {"foo"=>"bar","baz"=>"buz"} + # #=> {"foo"=>"bar", "baz"=>"buz"} + # puts config.to_s + # #=> [ default ] + # # foo=bar + # # baz=buz + # + # It's important to note that this will essentially merge any of the keys + # in _pairs_ with the existing _section_. For example: + # + # config['default'] + # #=> {"foo"=>"bar", "baz"=>"buz"} + # config['default'] = {"foo" => "changed"} + # #=> {"foo"=>"changed"} + # config['default'] + # #=> {"foo"=>"changed", "baz"=>"buz"} + # + def []=(section, pairs) + check_modify + set_section(section, pairs) + end + + def set_section(section, pairs) # :nodoc: + hash = @data[section] ||= {} + pairs.each do |key, value| + hash[key] = value + end + end + + ## + # Get the names of all sections in the current configuration + def sections + @data.keys + end + + ## + # Get the parsable form of the current configuration + # + # Given the following configuration being created: + # + # config = OpenSSL::Config.new + # #=> # + # config['default'] = {"foo"=>"bar","baz"=>"buz"} + # #=> {"foo"=>"bar", "baz"=>"buz"} + # puts config.to_s + # #=> [ default ] + # # foo=bar + # # baz=buz + # + # You can parse get the serialized configuration using #to_s and then parse + # it later: + # + # serialized_config = config.to_s + # # much later... + # new_config = OpenSSL::Config.parse(serialized_config) + # #=> # + # puts new_config + # #=> [ default ] + # foo=bar + # baz=buz + # + def to_s + ary = [] + @data.keys.sort.each do |section| + ary << "[ #{section} ]\n" + @data[section].keys.each do |key| + ary << "#{key}=#{@data[section][key]}\n" + end + ary << "\n" + end + ary.join + end + + ## + # For a block. + # + # Receive the section and its pairs for the current configuration. + # + # config.each do |section, key, value| + # # ... + # end + # + def each + @data.each do |section, hash| + hash.each do |key, value| + yield [section, key, value] + end + end + end + + ## + # String representation of this configuration object, including the class + # name and its sections. + def inspect + "#<#{self.class.name} sections=#{sections.inspect}>" + end + + protected + + def data # :nodoc: + @data + end + + private + + def initialize_copy(other) + @data = other.data.dup + end + + def check_modify + warn "#{caller(2, 1)[0]}: warning: do not modify OpenSSL::Config; this " \ + "method is deprecated and will be removed in a future release." + raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen? + end + + def get_key_string(section, key) + Config.get_key_string(@data, section, key) + end end - class ConfigError < OpenSSLError; end end diff --git a/lib/openssl/digest.rb b/lib/openssl/digest.rb index a1b40772..9208b496 100644 --- a/lib/openssl/digest.rb +++ b/lib/openssl/digest.rb @@ -1,9 +1,73 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# 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 'LICENCE'.) +#++ + +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. + # + # === Examples + # + # OpenSSL::Digest.digest("SHA256", "abc") + # + # which is equivalent to: + # + # 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 + # + # Digest("Foo") + # # => NameError: wrong constant name Foo + + def Digest(name) + OpenSSL::Digest.const_get(name) + end + + module_function :Digest + +end # OpenSSL diff --git a/lib/openssl/hmac.rb b/lib/openssl/hmac.rb new file mode 100644 index 00000000..f80174e9 --- /dev/null +++ b/lib/openssl/hmac.rb @@ -0,0 +1,78 @@ +# 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 unless method_defined?(:digest) # JRuby + + # :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 unless method_defined?(:hexdigest) # JRuby + + # :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 diff --git a/lib/openssl/marshal.rb b/lib/openssl/marshal.rb new file mode 100644 index 00000000..af564719 --- /dev/null +++ b/lib/openssl/marshal.rb @@ -0,0 +1,30 @@ +# 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 'LICENCE'.) +#++ +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 diff --git a/lib/openssl/pkcs5.rb b/lib/openssl/pkcs5.rb index cbfddea3..87c25cfb 100644 --- a/lib/openssl/pkcs5.rb +++ b/lib/openssl/pkcs5.rb @@ -1,10 +1,9 @@ +# frozen_string_literal: true #-- # Ruby/OpenSSL Project # Copyright (C) 2017 Ruby/OpenSSL Project Authors #++ -# JOpenSSL has these - here for explicit require 'openssl/pkcs5' compatibility - # module OpenSSL # module PKCS5 # module_function @@ -12,7 +11,8 @@ # # 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) +# OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter, +# length: keylen, hash: digest) # end # # def pbkdf2_hmac_sha1(pass, salt, iter, keylen) diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb index 370bb9d6..c935b72a 100644 --- a/lib/openssl/pkey.rb +++ b/lib/openssl/pkey.rb @@ -1,5 +1,42 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -else - raise LoadError, "no such file to load -- openssl/pkey" -end \ No newline at end of file +# frozen_string_literal: true +#-- +# Ruby/OpenSSL Project +# Copyright (C) 2017 Ruby/OpenSSL Project Authors +#++ + +require_relative 'marshal' + +module OpenSSL::PKey + class DH + include OpenSSL::Marshal + end + + class DSA + include OpenSSL::Marshal + end + + if defined?(EC) + class EC + include OpenSSL::Marshal + 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 + + class RSA + include OpenSSL::Marshal + end +end diff --git a/lib/openssl/ssl-internal.rb b/lib/openssl/ssl-internal.rb deleted file mode 100644 index 1717760b..00000000 --- a/lib/openssl/ssl-internal.rb +++ /dev/null @@ -1,5 +0,0 @@ -if RUBY_VERSION > '2.1' - raise LoadError, "no such library in #{RUBY_VERSION}: openssl/ssl-internal.rb" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index a1b40772..991f05e3 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -1,9 +1,543 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# 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 'LICENCE'.) +=end + +require "openssl/buffering" +require "io/nonblock" +require "socket" + +module OpenSSL + module SSL + class SSLContext + DEFAULT_PARAMS = { # :nodoc: + :min_version => OpenSSL::SSL::TLS1_VERSION, + :verify_mode => OpenSSL::SSL::VERIFY_PEER, + :verify_hostname => true, + :options => OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_COMPRESSION + } + + # JRuby does not want this non (recent) OpenSSL fallback to happen + # if !(OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") && + # OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000) + # DEFAULT_PARAMS.merge!( + # ciphers: %w{ + # ECDHE-ECDSA-AES128-GCM-SHA256 + # ECDHE-RSA-AES128-GCM-SHA256 + # ECDHE-ECDSA-AES256-GCM-SHA384 + # ECDHE-RSA-AES256-GCM-SHA384 + # DHE-RSA-AES128-GCM-SHA256 + # DHE-DSS-AES128-GCM-SHA256 + # DHE-RSA-AES256-GCM-SHA384 + # DHE-DSS-AES256-GCM-SHA384 + # ECDHE-ECDSA-AES128-SHA256 + # ECDHE-RSA-AES128-SHA256 + # ECDHE-ECDSA-AES128-SHA + # ECDHE-RSA-AES128-SHA + # ECDHE-ECDSA-AES256-SHA384 + # ECDHE-RSA-AES256-SHA384 + # ECDHE-ECDSA-AES256-SHA + # ECDHE-RSA-AES256-SHA + # DHE-RSA-AES128-SHA256 + # DHE-RSA-AES256-SHA256 + # DHE-RSA-AES128-SHA + # DHE-RSA-AES256-SHA + # DHE-DSS-AES128-SHA256 + # DHE-DSS-AES256-SHA256 + # DHE-DSS-AES128-SHA + # DHE-DSS-AES256-SHA + # AES128-GCM-SHA256 + # AES256-GCM-SHA384 + # AES128-SHA256 + # AES256-SHA256 + # AES128-SHA + # AES256-SHA + # }.join(":"), + # ) + # end + + if defined?(OpenSSL::PKey::DH) + DEFAULT_2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY +JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab +VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 +YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 +1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD +7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== +-----END DH PARAMETERS----- + _end_of_pem_ + private_constant :DEFAULT_2048 + + DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: + warn "using default DH parameters." if $VERBOSE + DEFAULT_2048 + } + end + + DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc: + DEFAULT_CERT_STORE.set_default_paths + DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + + # A callback invoked when DH parameters are required 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. + attr_accessor :tmp_dh_callback + + # A callback invoked at connect time to distinguish between multiple + # server names. + # + # The callback is invoked with an SSLSocket and a server name. The + # callback must return an SSLContext for the server name or nil. + attr_accessor :servername_cb + + # 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.options |= OpenSSL::SSL::OP_ALL + # 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 + self.cert_store = DEFAULT_CERT_STORE + end + end + return params + end + + # 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". + # + # Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v* + # options by #options= once you have called #min_version= or + # #max_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 + def min_version=(version) + set_minmax_proto_version(version, @max_proto_version ||= nil) + @min_proto_version = version + end + + # 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. + def max_version=(version) + set_minmax_proto_version(@min_proto_version ||= nil, version) + @max_proto_version = version + 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 + # set_minmax_proto_version(version, version) + # @min_proto_version = @max_proto_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 + + # METHODS setup from native (JRuby) + # 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 setsockopt(level, optname, optval) + to_io.setsockopt(level, optname, optval) + end + + def getsockopt(level, optname) + to_io.getsockopt(level, optname) + end + + def fcntl(*args) + to_io.fcntl(*args) + end + + def closed? + to_io.closed? + end + + def do_not_reverse_lookup=(flag) + to_io.do_not_reverse_lookup = flag + end + end + + def verify_certificate_identity(cert, hostname) + should_verify_common_name = true + cert.extensions.each{|ext| + next if ext.oid != "subjectAltName" + ext.value.split(/,\s+/).each { |general_name| + #case san.tag + # MRI 2.2.3 (JRuby parses ASN.1 differently) + #when 2 # dNSName in GeneralName (RFC5280) + if /\ADNS:(.*)/ =~ general_name + should_verify_common_name = false + return true if verify_hostname(hostname, $1) + # MRI 2.2.3 (JRuby parses ASN.1 differently) + #when 7 # iPAddress in GeneralName (RFC5280) + elsif /\AIP(?: Address)?:(.*)/ =~ general_name + should_verify_common_name = false + return true if $1 == hostname + # NOTE: bellow logic makes little sense JRuby reads exts differently + # follows GENERAL_NAME_print() in x509v3/v3_alt.c + # if san.value.size == 4 || san.value.size == 16 + # begin + # return true if $1 == IPAddr.new(hostname).to_s + # 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 unless method_defined? :session # JRuby + + private + + def using_anon_cipher? + ctx = OpenSSL::SSL::SSLContext.new + ctx.ciphers = "aNULL" + ctx.ciphers.include?(cipher) + end + + def client_cert_cb + @context.client_cert_cb + end + + def tmp_dh_callback + @context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK + 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).unpack('H*')[0] + @ctx.session_id_context = session_id + end + @start_immediately = true + end + + # Returns the TCPServer passed to the SSLServer when initialized. + def to_io + @svr + end + + # See TCPServer#listen for details. + def listen(backlog=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 diff --git a/lib/openssl/x509-internal.rb b/lib/openssl/x509-internal.rb deleted file mode 100644 index b444d193..00000000 --- a/lib/openssl/x509-internal.rb +++ /dev/null @@ -1,5 +0,0 @@ -if RUBY_VERSION > '2.1' - raise LoadError, "no such library in #{RUBY_VERSION}: openssl/x509-internal.rb" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb index a1b40772..1f378b48 100644 --- a/lib/openssl/x509.rb +++ b/lib/openssl/x509.rb @@ -1,9 +1,391 @@ -if RUBY_VERSION > '2.3' - load "jopenssl23/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.2' - load "jopenssl22/openssl/#{File.basename(__FILE__)}" -elsif RUBY_VERSION > '2.1' - load "jopenssl21/openssl/#{File.basename(__FILE__)}" -else - load "jopenssl19/openssl/#{File.basename(__FILE__)}" -end \ No newline at end of file +# 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 'LICENCE'.) +#++ + +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 RFC5280 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.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&.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier + end + end + + 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 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 diff --git a/mvnw b/mvnw new file mode 100755 index 00000000..8d937f4c --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 00000000..f80fbad3 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 2cbc1650..3881bc14 100644 --- a/pom.xml +++ b/pom.xml @@ -2,16 +2,16 @@ - 4.0.0 rubygems jruby-openssl - 0.10.3.dev-SNAPSHOT + 0.15.6.dev-SNAPSHOT gem JRuby OpenSSL JRuby-OpenSSL is an add-on gem for JRuby that emulates the Ruby OpenSSL native library. @@ -60,65 +60,56 @@ DO NOT MODIFIY - GENERATED CODE - 1.62 + 1.81 ${maven.test.skip} ${bc.versions} - 1.1.6 + 3.0.2 -W0 - 9.1.17.0 - 9.1.17.0 - 0.2.1 + 9.2.19.0 + 9.2.19.0 + 2.0.2 + 2.0.2 pom.xml - true + false src/test/ruby/**/test_*.rb - rubygems - jar-dependencies - [0.1,0.99999] - gem - test - - - rubygems - mocha - [1.4,2.0) - gem - test - - - rubygems - ruby-maven - [3.0,3.99999] - gem - test + org.bouncycastle + bcprov-jdk18on + 1.81 org.bouncycastle - bcprov-jdk15on - 1.62 + bcpkix-jdk18on + 1.81 org.bouncycastle - bcpkix-jdk15on - 1.62 + bctls-jdk18on + 1.81 org.bouncycastle - bctls-jdk15on - 1.62 + bcutil-jdk18on + 1.81 org.jruby jruby-core - 1.7.20 + 9.1.11.0 provided + + javax.annotation + javax.annotation-api + 1.3.1 + compile + junit junit - 4.11 + [4.13.1,) test @@ -131,12 +122,12 @@ DO NOT MODIFIY - GENERATED CODE - org.torquebox.mojo + org.jruby.maven mavengem-wagon ${mavengem.wagon.version} - de.saumya.mojo + org.jruby.maven gem-with-jar-extension ${jruby.plugins.version} @@ -190,12 +181,12 @@ DO NOT MODIFIY - GENERATED CODE - de.saumya.mojo + org.jruby.maven gem-maven-plugin ${jruby.plugins.version} - default-initialize + default-package false something-which-does-not-exists @@ -229,7 +220,7 @@ DO NOT MODIFIY - GENERATED CODE java compile - -Djruby.bytecode.version=1.7 + -Djruby.bytecode.version=1.8 -classpath org.jruby.anno.InvokerGenerator @@ -260,7 +251,7 @@ DO NOT MODIFIY - GENERATED CODE maven-compiler-plugin - 3.1 + 3.9.0 compile-populators @@ -274,14 +265,16 @@ DO NOT MODIFIY - GENERATED CODE true + -XDignore.symbol.file=true - 1.7 - 1.7 + 1.8 + 1.8 + 8 UTF-8 true true @@ -293,9 +286,6 @@ DO NOT MODIFIY - GENERATED CODE org.jruby.anno.AnnotationBinder - - -XDignore.symbol.file=true - @@ -361,7 +351,7 @@ DO NOT MODIFIY - GENERATED CODE - de.saumya.mojo + org.jruby.maven runit-maven-plugin ${jruby.plugins.version} @@ -379,28 +369,7 @@ DO NOT MODIFIY - GENERATED CODE - module-info - - [9,) - - - - - maven-compiler-plugin - 3.1 - - 9 - 1.7 - - module-info.java - - - - - - - - test-1.7.20 + test-9.1.2.0 @@ -433,279 +402,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.20 - - - - test-1.7.22 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.22 - - - - test-1.7.23 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.23 - - - - test-1.7.24 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.24 - - - - test-1.7.25 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.25 - - - - test-1.7.26 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.26 - - - - test-1.7.27 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 1.9,2.0 - 1.7.27 - - - - test-9.0.1.0 - - - - maven-invoker-plugin - 1.8 - - - - install - run - - - integration - - */pom.xml - - true - - ${jruby.versions} - ${jruby.modes} - ${project.version} - ${bc.versions} - ${runit.dir} - - - - - - - - - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.0.1.0 - 9.0.1.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.1.2.0 + 9.1.2.0 - test-9.0.5.0 + test-9.1.8.0 @@ -737,13 +440,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.0.5.0 - 9.0.5.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.1.8.0 + 9.1.8.0 - test-9.1.2.0 + test-9.1.12.0 @@ -775,13 +478,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.1.2.0 - 9.1.2.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.1.12.0 + 9.1.12.0 - test-9.1.8.0 + test-9.1.16.0 @@ -813,13 +516,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.1.8.0 - 9.1.8.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.1.16.0 + 9.1.16.0 - test-9.1.12.0 + test-9.1.17.0 @@ -851,13 +554,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.1.12.0 - 9.1.12.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.1.17.0 + 9.1.17.0 - test-9.1.16.0 + test-9.2.0.0 @@ -889,13 +592,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.1.16.0 - 9.1.16.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.2.0.0 + 9.2.0.0 - test-9.1.17.0 + test-9.2.5.0 @@ -927,13 +630,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.1.17.0 - 9.1.17.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.2.5.0 + 9.2.5.0 - test-9.2.0.0 + test-9.2.10.0 @@ -965,13 +668,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.2.0.0 - 9.2.0.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.2.10.0 + 9.2.10.0 - test-9.2.5.0 + test-9.2.17.0 @@ -1003,13 +706,13 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.2.5.0 - 9.2.5.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.2.17.0 + 9.2.17.0 - test-9.2.6.0 + test-9.2.19.0 @@ -1041,9 +744,9 @@ DO NOT MODIFIY - GENERATED CODE - 1.55,1.56,1.57,1.58,1.59,1.60,1.61 - 9.2.6.0 - 9.2.6.0 + 1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68 + 9.2.19.0 + 9.2.19.0 diff --git a/src/main/java/org/jruby/ext/openssl/ASN1.java b/src/main/java/org/jruby/ext/openssl/ASN1.java index a35e199f..3e7417e4 100644 --- a/src/main/java/org/jruby/ext/openssl/ASN1.java +++ b/src/main/java/org/jruby/ext/openssl/ASN1.java @@ -31,20 +31,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.text.ParseException; -import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.bouncycastle.asn1.*; - import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyBignum; @@ -550,12 +546,12 @@ private static Map getSymLookup(final Ruby runtime { "NUMERICSTRING", org.bouncycastle.asn1.DERNumericString.class, "NumericString" }, { "PRINTABLESTRING", org.bouncycastle.asn1.DERPrintableString.class, "PrintableString" }, { "T61STRING", org.bouncycastle.asn1.DERT61String.class, "T61String" }, - { "VIDEOTEXSTRING", null, "VideotexString" }, + { "VIDEOTEXSTRING", org.bouncycastle.asn1.DERVideotexString.class, "VideotexString" }, { "IA5STRING", org.bouncycastle.asn1.DERIA5String.class, "IA5String" }, { "UTCTIME", org.bouncycastle.asn1.DERUTCTime.class, "UTCTime" }, { "GENERALIZEDTIME", org.bouncycastle.asn1.DERGeneralizedTime.class, "GeneralizedTime" }, - { "GRAPHICSTRING", null, "GraphicString" }, - { "ISO64STRING", null, "ISO64String" }, + { "GRAPHICSTRING", org.bouncycastle.asn1.DERGraphicString.class, "GraphicString" }, + { "ISO64STRING", org.bouncycastle.asn1.DERVisibleString.class, "ISO64String" }, { "GENERALSTRING", org.bouncycastle.asn1.DERGeneralString.class, "GeneralString" }, // OpenSSL::ASN1::UNIVERSALSTRING (28) : { "UNIVERSALSTRING", org.bouncycastle.asn1.DERUniversalString.class, "UniversalString" }, @@ -563,6 +559,30 @@ private static Map getSymLookup(final Ruby runtime // OpenSSL::ASN1::BMPSTRING (30) : { "BMPSTRING", org.bouncycastle.asn1.DERBMPString.class, "BMPString" }}; + final static int EOC = 0; // OpenSSL::ASN1::EOC (0) + final static int BOOLEAN = 1; // OpenSSL::ASN1::BOOLEAN (1) + final static int INTEGER = 2; // OpenSSL::ASN1::INTEGER (2) + final static int BIT_STRING = 3; // OpenSSL::ASN1::BIT_STRING (3) + final static int OCTET_STRING = 4; // OpenSSL::ASN1::OCTET_STRING (4) + final static int NULL = 5; // OpenSSL::ASN1::NULL (5) + final static int OBJECT = 6; // OpenSSL::ASN1::OBJECT (6) + final static int ENUMERATED = 10; // OpenSSL::ASN1::ENUMERATED (10) + final static int UTF8STRING = 12; // OpenSSL::ASN1::UTF8STRING (12) + final static int SEQUENCE = 16; // OpenSSL::ASN1::SEQUENCE (16) + final static int SET = 17; // OpenSSL::ASN1::SET (17) + final static int NUMERICSTRING = 18; // OpenSSL::ASN1::NUMERICSTRING (18) + final static int PRINTABLESTRING = 19; // OpenSSL::ASN1::PRINTABLESTRING (19) + final static int T61STRING = 20; // OpenSSL::ASN1::T61STRING (20) + final static int VIDEOTEXSTRING = 21; // OpenSSL::ASN1::VIDEOTEXSTRING (21) + final static int IA5STRING = 22; // OpenSSL::ASN1::IA5STRING (22) + final static int UTCTIME = 23; // OpenSSL::ASN1::UTCTIME (23) + final static int GENERALIZEDTIME = 24; // OpenSSL::ASN1::GENERALIZEDTIME (24) + final static int GRAPHICSTRING = 25; // OpenSSL::ASN1::GRAPHICSTRING (25) + final static int ISO64STRING = 26; // OpenSSL::ASN1::ISO64STRING (26) + final static int GENERALSTRING = 27; // OpenSSL::ASN1::GENERALSTRING (27) + final static int UNIVERSALSTRING = 28; // OpenSSL::ASN1::UNIVERSALSTRING (28) + final static int BMPSTRING = 30; // OpenSSL::ASN1::BMPSTRING (30) + private final static Map, Integer> JCLASS_TO_ID = new HashMap, Integer>(24, 1); private final static Map RCLASS_TO_ID = new HashMap(28, 1); @@ -575,33 +595,35 @@ private static Map getSymLookup(final Ruby runtime if ( info[2] != null ) { RCLASS_TO_ID.put((String) info[2], Integer.valueOf(i)); } + + switch (i) { + case EOC: assert "EOC".equals(info[0]); break; + case BOOLEAN: assert "BOOLEAN".equals(info[0]); break; + case INTEGER: assert "INTEGER".equals(info[0]); break; + case BIT_STRING: assert "BIT_STRING".equals(info[0]); break; + case OCTET_STRING: assert "OCTET_STRING".equals(info[0]); break; + case NULL: assert "NULL".equals(info[0]); break; + case OBJECT: assert "OBJECT".equals(info[0]); break; + case ENUMERATED: assert "ENUMERATED".equals(info[0]); break; + case UTF8STRING: assert "UTF8STRING".equals(info[0]); break; + case SEQUENCE: assert "SEQUENCE".equals(info[0]); break; + case SET: assert "SET".equals(info[0]); break; + case NUMERICSTRING: assert "NUMERICSTRING".equals(info[0]); break; + case PRINTABLESTRING: assert "PRINTABLESTRING".equals(info[0]); break; + case T61STRING: assert "T61STRING".equals(info[0]); break; + case VIDEOTEXSTRING: assert "VIDEOTEXSTRING".equals(info[0]); break; + case IA5STRING: assert "IA5STRING".equals(info[0]); break; + case UTCTIME: assert "UTCTIME".equals(info[0]); break; + case GENERALIZEDTIME: assert "GENERALIZEDTIME".equals(info[0]); break; + case GRAPHICSTRING: assert "GRAPHICSTRING".equals(info[0]); break; + case ISO64STRING: assert "ISO64STRING".equals(info[0]); break; + case GENERALSTRING: assert "GENERALSTRING".equals(info[0]); break; + case UNIVERSALSTRING: assert "UNIVERSALSTRING".equals(info[0]); break; + case BMPSTRING: assert "BMPSTRING".equals(info[0]); break; + } } } - private final static int EOC = 0; // OpenSSL::ASN1::EOC (0) - //private final static int BOOLEAN = 1; // OpenSSL::ASN1::BOOLEAN (1) - //private final static int INTEGER = 2; // OpenSSL::ASN1::INTEGER (2) - private final static int BIT_STRING = 3; // OpenSSL::ASN1::BIT_STRING (3) - private final static int OCTET_STRING = 4; // OpenSSL::ASN1::OCTET_STRING (4) - //private final static int NULL = 5; // OpenSSL::ASN1::NULL (5) - //private final static int OBJECT = 6; // OpenSSL::ASN1::OBJECT (6) - //private final static int ENUMARATED = 10; // OpenSSL::ASN1::ENUMERATED (10) - //private final static int UTFSTRING = 12; // OpenSSL::ASN1::UTF8STRING (12) - private final static int SEQUENCE = 16; // OpenSSL::ASN1::SEQUENCE (16) - private final static int SET = 17; // OpenSSL::ASN1::SET (17) - //private final static int NUMERICSTRING = 18; // 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) - private static Integer typeId(Class type) { Integer id = null; while ( type != Object.class && id == null ) { @@ -633,23 +655,9 @@ static Class typeClass(final int typeId) { return (Class) ASN1_INFO[typeId][1]; } - static ASN1Encodable typeInstance(Class type, Object value) - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method getInstance = null; - try { - getInstance = type.getMethod("getInstance", Object.class); - } - catch (NoSuchMethodException e) { - Class superType = type.getSuperclass(); - try { - if ( superType != Object.class ) { - getInstance = type.getSuperclass().getMethod("getInstance", Object.class); - } - } - catch (NoSuchMethodException e2) { } - if ( getInstance == null ) throw e; - } - return (ASN1Encodable) getInstance.invoke(null, value); + static Class typeClassSafe(final int typeId) { + if (typeId >= ASN1_INFO.length || typeId < 0) return null; + return typeClass(typeId); } public static void createASN1(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { @@ -677,21 +685,28 @@ public static void createASN1(final Ruby runtime, final RubyModule OpenSSL, fina _ASN1Data.addReadWriteAttribute(context, "value"); _ASN1Data.addReadWriteAttribute(context, "tag"); _ASN1Data.addReadWriteAttribute(context, "tag_class"); + _ASN1Data.addReadWriteAttribute(context, "indefinite_length"); + _ASN1Data.defineAlias( "infinite_length", "indefinite_length"); + _ASN1Data.defineAlias( "infinite_length=", "indefinite_length="); _ASN1Data.defineAnnotatedMethods(ASN1Data.class); final ObjectAllocator primitiveAllocator = Primitive.ALLOCATOR; RubyClass Primitive = ASN1.defineClassUnder("Primitive", _ASN1Data, primitiveAllocator); Primitive.addReadWriteAttribute(context, "tagging"); - Primitive.addReadAttribute(context, "infinite_length"); + Primitive.undefineMethod("infinite_length="); + Primitive.undefineMethod("indefinite_length="); Primitive.defineAnnotatedMethods(Primitive.class); final ObjectAllocator constructiveAllocator = Constructive.ALLOCATOR; RubyClass Constructive = ASN1.defineClassUnder("Constructive", _ASN1Data, constructiveAllocator); Constructive.includeModule( runtime.getModule("Enumerable") ); Constructive.addReadWriteAttribute(context, "tagging"); - Constructive.addReadWriteAttribute(context, "infinite_length"); Constructive.defineAnnotatedMethods(Constructive.class); + final ObjectAllocator eocAllocator = EndOfContent.ALLOCATOR; + RubyClass EndOfContent = ASN1.defineClassUnder("EndOfContent", _ASN1Data, eocAllocator); + EndOfContent.defineAnnotatedMethods(EndOfContent.class); + ASN1.defineClassUnder("Boolean", Primitive, primitiveAllocator); // OpenSSL::ASN1::Boolean <=> value is a Boolean ASN1.defineClassUnder("Integer", Primitive, primitiveAllocator); // OpenSSL::ASN1::Integer <=> value is a Number ASN1.defineClassUnder("Null", Primitive, primitiveAllocator); // OpenSSL::ASN1::Null <=> value is always nil @@ -716,10 +731,8 @@ public static void createASN1(final Ruby runtime, final RubyModule OpenSSL, fina ASN1.defineClassUnder("UTCTime", Primitive, primitiveAllocator); // OpenSSL::ASN1::UTCTime <=> value is a Time ASN1.defineClassUnder("GeneralizedTime", Primitive, primitiveAllocator); // OpenSSL::ASN1::GeneralizedTime <=> value is a Time - ASN1.defineClassUnder("EndOfContent", Primitive, primitiveAllocator); // OpenSSL::ASN1::EndOfContent <=> value is always nil - - RubyClass ObjectId = ASN1.defineClassUnder("ObjectId", Primitive, primitiveAllocator); - ObjectId.defineAnnotatedMethods(ObjectId.class); + ASN1.defineClassUnder("ObjectId", Primitive, primitiveAllocator). + defineAnnotatedMethods(ObjectId.class); ASN1.defineClassUnder("Sequence", Constructive, Constructive.getAllocator()); ASN1.defineClassUnder("Set", Constructive, Constructive.getAllocator()); @@ -829,7 +842,7 @@ public static IRubyObject fact_BMPString(ThreadContext context, IRubyObject self return newInstance(context, self, "BMPString", args); } - @JRubyMethod(name="Nul", module=true, rest=true) + @JRubyMethod(name={"Null", "Nul"}, module=true, rest=true) // TODO Nul name should be dropped public static IRubyObject fact_Null(ThreadContext context, IRubyObject self, IRubyObject[] args) { return newInstance(context, self, "Null", args); } @@ -849,6 +862,11 @@ public static IRubyObject fact_GeneralizedTime(ThreadContext context, IRubyObjec return newInstance(context, self, "GeneralizedTime", args); } + @JRubyMethod(name="EndOfContent", module=true, rest=true) + public static IRubyObject fact_EndOfContent(ThreadContext context, IRubyObject self, IRubyObject[] args) { + return newInstance(context, self, "EndOfContent", args); + } + @JRubyMethod(name="Sequence", module=true, rest=true) public static IRubyObject fact_Sequence(ThreadContext context, IRubyObject self, IRubyObject[] args) { return newInstance(context, self, "Sequence", args); @@ -897,6 +915,14 @@ public static RubyString oid(final ThreadContext context, final IRubyObject self return runtime.newString( getObjectID(runtime, self.callMethod(context, "value").toString()).getId() ); } + @JRubyMethod(name = "==") + public static IRubyObject eq(final ThreadContext context, final IRubyObject self, final IRubyObject other) { + if (!other.getMetaClass().equals(_ASN1(context.runtime).getClass("ObjectId"))) { + return context.runtime.getFalse(); + } + return self.callMethod(context, "value").op_eqq(context, other.callMethod(context, "value")); + } + private static RubyString name(final ThreadContext context, IRubyObject value, final boolean longName) { final Ruby runtime = context.runtime; @@ -925,46 +951,63 @@ static IRubyObject decodeObject(final ThreadContext context, return ASN1.getClass("Integer").newInstance(context, val, Block.NULL_BLOCK); } - if ( obj instanceof DERBitString ) { - final DERBitString derObj = (DERBitString) obj; - RubyString str = runtime.newString( new ByteList(derObj.getBytes(), false) ); + if ( obj instanceof ASN1BitString ) { + final ASN1BitString derObj = (ASN1BitString) obj; + RubyString str = runtime.newString(new ByteList(derObj.getBytes(), false)); IRubyObject bitString = ASN1.getClass("BitString").newInstance(context, str, Block.NULL_BLOCK); - bitString.callMethod(context, "unused_bits=", runtime.newFixnum( derObj.getPadBits() )); + bitString.getInstanceVariables().setInstanceVariable("@unused_bits", runtime.newFixnum(derObj.getPadBits())); return bitString; } if ( obj instanceof ASN1String ) { final Integer typeId = typeId( obj.getClass() ); String type = typeId == null ? null : (String) ( ASN1_INFO[typeId][2] ); final ByteList bytes; - if ( obj instanceof DERUTF8String ) { + if ( obj instanceof ASN1UTF8String ) { if ( type == null ) type = "UTF8String"; - bytes = new ByteList(((DERUTF8String) obj).getString().getBytes("UTF-8"), false); + bytes = new ByteList(((ASN1UTF8String) obj).getString().getBytes(StandardCharsets.UTF_8), false); + } + else if ( obj instanceof ASN1UniversalString ) { + if ( type == null ) type = "UniversalString"; + bytes = new ByteList(((ASN1UniversalString) obj).getOctets(), false); + } + else if ( obj instanceof ASN1BMPString ) { + if ( type == null ) type = "BMPString"; + final String val = ((ASN1BMPString) obj).getString(); + final byte[] valBytes = new byte[val.length() * 2]; + for (int i = 0; i < val.length(); i++) { + char c = val.charAt(i); + valBytes[i * 2] = (byte) ((c >> 8) & 0xff); + valBytes[i * 2 + 1] = (byte) (c & 0xff); + } + bytes = new ByteList(valBytes, false); } else { if ( type == null ) { - if ( obj instanceof DERNumericString ) { + if ( obj instanceof ASN1NumericString ) { type = "NumericString"; } - else if ( obj instanceof DERPrintableString ) { + else if ( obj instanceof ASN1PrintableString ) { type = "PrintableString"; } - else if ( obj instanceof DERIA5String ) { + else if ( obj instanceof ASN1IA5String ) { type = "IA5String"; } - else if ( obj instanceof DERT61String ) { + else if ( obj instanceof ASN1T61String ) { type = "T61String"; } - else if ( obj instanceof DERGeneralString ) { + else if ( obj instanceof ASN1GeneralString ) { type = "GeneralString"; } - else if ( obj instanceof DERUniversalString ) { - type = "UniversalString"; + else if ( obj instanceof ASN1VideotexString ) { + type = "VideotexString"; + } + else if ( obj instanceof ASN1VisibleString ) { + type = "ISO64String"; } - else if ( obj instanceof DERBMPString ) { - type = "BMPString"; + else if ( obj instanceof ASN1GraphicString ) { + type = "GraphicString"; } else { - // NOTE "VideotexString", "GraphicString", "ISO64String" not-handled in BC ! throw new IllegalArgumentException("could not handle ASN1 string type: " + obj + " (" + obj.getClass().getName() + ")"); } } @@ -973,11 +1016,6 @@ else if ( obj instanceof DERBMPString ) { return ASN1.getClass(type).newInstance(context, runtime.newString(bytes), Block.NULL_BLOCK); } - //if ( obj instanceof DEROctetString ) { - // byte[] octets = ((ASN1OctetString) obj).getOctets(); - // if ( (octets[0] & 0xFF) == 0xD1 ) Thread.dumpStack(); - //} - if ( obj instanceof ASN1OctetString ) { final ByteList octets = new ByteList(((ASN1OctetString) obj).getOctets(), false); // NOTE: sometimes MRI does include the tag but it really should not ;( ! @@ -1000,14 +1038,6 @@ else if ( obj instanceof DERBMPString ) { final RubyTime time = RubyTime.newTime(runtime, adjustedTime.getTime()); return ASN1.getClass("UTCTime").newInstance(context, time, Block.NULL_BLOCK); } - // NOTE: keep for BC versions compatibility ... extends ASN1UTCTime (since BC 1.51) - if ( obj instanceof DERUTCTime ) { - final Date adjustedTime; - try { adjustedTime = ((DERUTCTime) obj).getAdjustedDate(); } - catch (ParseException e) { throw new IOException(e); } - final RubyTime time = RubyTime.newTime(runtime, adjustedTime.getTime()); - return ASN1.getClass("UTCTime").newInstance(context, time, Block.NULL_BLOCK); - } if ( obj instanceof ASN1GeneralizedTime ) { final Date generalTime; @@ -1016,39 +1046,40 @@ else if ( obj instanceof DERBMPString ) { final RubyTime time = RubyTime.newTime(runtime, generalTime.getTime()); return ASN1.getClass("GeneralizedTime").newInstance(context, time, Block.NULL_BLOCK); } - // NOTE: keep for BC versions compatibility ... extends ASN1GeneralizedTime (since BC 1.51) - //if ( obj instanceof DERGeneralizedTime ) { - // final Date generalTime; - // try { - // generalTime = ((DERGeneralizedTime) obj).getDate(); - // } - // catch (ParseException e) { throw new IOException(e); } - // final RubyTime time = RubyTime.newTime(runtime, generalTime.getTime()); - // return ASN1.getClass("GeneralizedTime").newInstance(context, time, Block.NULL_BLOCK); - //} if ( obj instanceof ASN1ObjectIdentifier ) { final String objId = ((ASN1ObjectIdentifier) obj).getId(); return ASN1.getClass("ObjectId").newInstance(context, runtime.newString(objId), Block.NULL_BLOCK); } - if ( obj instanceof ASN1TaggedObject ) { + if (obj instanceof ASN1TaggedObject) { final ASN1TaggedObject taggedObj = (ASN1TaggedObject) obj; - IRubyObject val = decodeObject(context, ASN1, taggedObj.getObject()); - IRubyObject tag = runtime.newFixnum( taggedObj.getTagNo() ); - IRubyObject tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); - final RubyArray valArr = runtime.newArray(val); - return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); - } - - if ( obj instanceof ASN1ApplicationSpecific ) { - final ASN1ApplicationSpecific appSpecific = (ASN1ApplicationSpecific) obj; - IRubyObject tag = runtime.newFixnum( appSpecific.getApplicationTag() ); - IRubyObject tag_class = runtime.newSymbol("APPLICATION"); - final ASN1Sequence sequence = (ASN1Sequence) appSpecific.getObject(SEQUENCE); - @SuppressWarnings("unchecked") - final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects()); - return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); + final IRubyObject tag = runtime.newFixnum(taggedObj.getTagNo()); + final IRubyObject tag_class; + switch (taggedObj.getTagClass()) { + case BERTags.PRIVATE: + tag_class = runtime.newSymbol("PRIVATE"); + break; + case BERTags.APPLICATION: + tag_class = runtime.newSymbol("APPLICATION"); + break; + case BERTags.CONTEXT_SPECIFIC: + tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); + break; + default: + tag_class = runtime.newSymbol("UNIVERSAL"); + break; + } + + try { + final ASN1Sequence sequence = (ASN1Sequence) taggedObj.getBaseUniversal(false, SEQUENCE); + @SuppressWarnings("unchecked") + final RubyArray valArr = decodeObjects(context, ASN1, sequence.getObjects()); + return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { valArr, tag, tag_class }, Block.NULL_BLOCK); + } catch (IllegalStateException e) { + IRubyObject val = decodeObject(context, ASN1, taggedObj.getBaseObject()).callMethod(context, "value"); + return ASN1.getClass("ASN1Data").newInstance(context, new IRubyObject[] { val, tag, tag_class }, Block.NULL_BLOCK); + } } if ( obj instanceof ASN1Sequence ) { @@ -1087,18 +1118,17 @@ public static IRubyObject decode(final ThreadContext context, return decodeImpl(context, (RubyModule) self, obj); } catch (IOException e) { - //throw context.runtime.newIOErrorFromException(e); - throw newASN1Error(context.runtime, e.getMessage()); + throw newASN1Error(context.runtime, e); } catch (IllegalArgumentException e) { debugStackTrace(context.runtime, e); - throw context.runtime.newArgumentError(e.getMessage()); + throw (RaiseException) context.runtime.newArgumentError(e.getMessage()).initCause(e); + } + catch (RuntimeException e) { + + debugStackTrace(context.runtime, e); + throw Utils.newRuntimeError(context.runtime, e); } - //catch (RuntimeException e) { - // final Ruby runtime = context.runtime; - // debugStackTrace(runtime, e); - // throw Utils.newRuntimeError(context.runtime, e); - //} } static IRubyObject decodeImpl(final ThreadContext context, IRubyObject obj) @@ -1134,26 +1164,64 @@ private BytesInputStream(final ByteList bytes) { } - private static IRubyObject decodeImpl(final ThreadContext context, - final RubyModule ASN1, final BytesInputStream in) throws IOException, IllegalArgumentException { + private static IRubyObject decodeImpl(final ThreadContext context, final RubyModule ASN1, final BytesInputStream in) + throws IOException, IllegalArgumentException { + final byte[] asn1 = in.bytes(); + int offset = in.offset(); + final int tag = asn1[offset] & 0xFF; + + if ( ( tag & BERTags.CONSTRUCTED ) == 0 ) { + return decodeObject(context, ASN1, readObject(in)); + } + // NOTE: need to handle OpenSSL::ASN1::Constructive wrapping by hand : - final Integer tag = getConstructiveTag(in.bytes(), in.offset()); - IRubyObject decoded = decodeObject(context, ASN1, readObject( in )); - if ( tag != null ) { // OpenSSL::ASN1::Constructive.new( arg ) : - final String type; List value = null; - if ( tag.intValue() == SEQUENCE ) { - //type = "Sequence"; // got a OpenSSL::ASN1::Sequence already : - return Constructive.setInfiniteLength(context, decoded); - } - else if ( tag.intValue() == SET ) { - //type = "Set"; // got a OpenSSL::ASN1::Set already : - return Constructive.setInfiniteLength(context, decoded); + int tagNo = tag & 0x1f; + if (tagNo == 0x1f) + { + tagNo = 0; + int b = asn1[ ++offset ]; + + // X.690-0207 8.1.2.4.2 + // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." + if ((b & 0x7f) == 0) // Note: -1 will pass + { + throw new IOException("corrupted stream - invalid high tag number found"); } - else { - type = "Constructive"; + + while ((b >= 0) && ((b & 0x80) != 0)) + { + tagNo |= (b & 0x7f); + tagNo <<= 7; + b = asn1[ ++offset ]; + } + + if (b < 0) + { + throw new IOException("EOF found inside tag value."); + } + + tagNo |= (b & 0x7f); + } + final int length = asn1[ ++offset ] & 0xFF; + final boolean isIndefiniteLength = length == 0x80; + IRubyObject decoded; + + decoded = decodeObject(context, ASN1, readObject(in)); + + final boolean isUniversal = ((ASN1Data) decoded).isUniversal(context); + + if (isIndefiniteLength) { + if (tagNo == BERTags.SEQUENCE || tagNo == BERTags.SET) { + return ASN1Data.setInfiniteLength(context, decoded); + } else if (isUniversal) { + decoded = Constructive.newInfiniteLength(context, context.runtime.newArray(decoded), tagNo); + } else { + if (decoded instanceof ASN1Data) { + return ASN1Data.setInfiniteLength(context, decoded); + } else { + decoded = ASN1Data.newInfiniteLength(context, context.runtime.newArray(decoded), tagNo, ((ASN1Data) decoded).tagClass()); + } } - if ( value == null ) value = Collections.singletonList(decoded); - return Constructive.newInfiniteConstructive(context, type, value, tag); } return decoded; } @@ -1172,8 +1240,7 @@ public static IRubyObject decode_all(final ThreadContext context, arr.append( decodeImpl(context, ASN1, in) ); } catch (IOException e) { - //throw context.runtime.newIOErrorFromException(e); - throw newASN1Error(context.runtime, e.getMessage()); + throw newASN1Error(context.runtime, e); } catch (IllegalArgumentException e) { debugStackTrace(context.runtime, e); @@ -1193,6 +1260,10 @@ public static RaiseException newASN1Error(Ruby runtime, String message) { return Utils.newError(runtime, _ASN1(runtime).getClass("ASN1Error"), message, false); } + static RaiseException newASN1Error(Ruby runtime, Throwable ex) { + return (RaiseException) newASN1Error(runtime, ex.getMessage()).initCause(ex); + } + static RubyModule _ASN1(final Ruby runtime) { return (RubyModule) runtime.getModule("OpenSSL").getConstant("ASN1"); } @@ -1207,89 +1278,6 @@ private static org.bouncycastle.asn1.ASN1Primitive readObject(final InputStream return new ASN1InputStream(bytes).readObject(); } - // NOTE: BC's ASNInputStream internals "reinvented" a bit : - private static Integer getConstructiveTag(final byte[] asn1, int offset) { - final int tag = asn1[ offset ] & 0xFF; - if ( ( tag & BERTags.CONSTRUCTED ) != 0 ) { // isConstructed - // - // calculate tag number - // - // readTagNumber(asn1, ++offset, tag) : - int tagNo = tag & 0x1f; - // - // with tagged object tag number is bottom 5 bits, or stored at the start of the content - // - if (tagNo == 0x1f) - { - tagNo = 0; - - int b = asn1[ ++offset ]; //s.read(); - - // X.690-0207 8.1.2.4.2 - // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." - if ((b & 0x7f) == 0) // Note: -1 will pass - { - return null; //throw new IOException("corrupted stream - invalid high tag number found"); - } - - while ((b >= 0) && ((b & 0x80) != 0)) - { - tagNo |= (b & 0x7f); - tagNo <<= 7; - b = asn1[ ++offset ]; //s.read(); - } - - if (b < 0) - { - return null; //throw new EOFException("EOF found inside tag value."); - } - - tagNo |= (b & 0x7f); - } - - // - // calculate length - // - final int length = asn1[ ++offset ] & 0xFF; - - if ( length == 0x80 ) { - // return -1; // indefinite-length encoding - } - else { - return null; - } - - if ((tag & BERTags.APPLICATION) != 0) { - //return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject(); - } - - if ((tag & BERTags.TAGGED) != 0) { - //return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject(); - } - - //System.out.println(" tagNo = 0x" + Integer.toHexString(tagNo)); - // TODO There are other tags that may be constructed (e.g. BIT_STRING) - switch (tagNo) { - case BERTags.SEQUENCE : - //return new BERSequenceParser(sp).getLoadedObject(); - return Integer.valueOf( SEQUENCE ); //return "Sequence"; - case BERTags.SET : - //return new BERSetParser(sp).getLoadedObject(); - return Integer.valueOf( SET ); //return "Set"; - case BERTags.OCTET_STRING : - return Integer.valueOf( OCTET_STRING ); - //return new BEROctetStringParser(sp).getLoadedObject(); - case BERTags.EXTERNAL : - //return new DERExternalParser(sp).getLoadedObject(); - default: - return Integer.valueOf( 0 ); //return "Constructive"; - //throw new IOException("unknown BER object encountered"); - } - } - - return null; - } - public static class ASN1Data extends RubyObject { private static final long serialVersionUID = 6117598347932209839L; @@ -1308,31 +1296,97 @@ public ASN1Data(Ruby runtime, RubyClass type) { @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, final IRubyObject value, final IRubyObject tag, final IRubyObject tag_class) { - checkTag(context.runtime, tag, tag_class, "UNIVERSAL"); + checkTag(context.runtime, tag, tag_class); this.callMethod(context, "tag=", tag); this.callMethod(context, "value=", value); this.callMethod(context, "tag_class=", tag_class); + this.setInstanceVariable("@indefinite_length", context.runtime.getFalse()); return this; } - private void checkTag(final Ruby runtime, final IRubyObject tag, - final IRubyObject tagClass, final String expected) { + static ASN1Data newInfiniteLength(final ThreadContext context, + final IRubyObject value, final int defaultTag, final IRubyObject tagClass) { + final Ruby runtime = context.runtime; + + final RubyClass klass = _ASN1(runtime).getClass("ASN1Data"); + final ASN1Data self = new Constructive(runtime, klass); + + ASN1Data.newInfiniteLengthImpl(context, self, value, defaultTag, tagClass); + return self; + } + + static void newInfiniteLengthImpl(final ThreadContext context, final ASN1Data self, final IRubyObject value, final int defaultTag, final IRubyObject tagClass) { + self.setInstanceVariable("@tag", context.runtime.newFixnum(defaultTag)); + self.setInstanceVariable("@value", value); + self.setInstanceVariable("@tag_class", tagClass); + self.setInstanceVariable("@tagging", context.nil); + + setInfiniteLength(context, self); + } + + static ASN1Data setInfiniteLength(final ThreadContext context, final IRubyObject constructive) { + final ASN1Data instance = ((ASN1Data) constructive); + final IRubyObject value = instance.value(context); + value.callMethod(context, "<<", EndOfContent.newInstance(context)); + instance.setInstanceVariable("@indefinite_length", context.runtime.getTrue()); + return instance; + } + + private void checkTag(final Ruby runtime, final IRubyObject tag, final IRubyObject tagClass) { if ( ! (tagClass instanceof RubySymbol) ) { throw newASN1Error(runtime, "invalid tag class"); } - if ( tagClass.toString().equals(expected) && RubyNumeric.fix2int(tag) > MAX_TAG_VALUE ) { - throw newASN1Error(runtime, "tag number for :" + expected + " too large"); + if ( "UNIVERSAL".equals(tagClass.toString()) && RubyNumeric.fix2int(tag) > MAX_TAG_VALUE ) { + throw newASN1Error(runtime, "tag number for :UNIVERSAL too large (" + tag + ")"); } } - boolean isEOC() { return false; } + private boolean isConstructive() { + return "Constructive".equals(getMetaClass().getRealClass().getBaseName()); + } + + boolean isInfiniteLength() { + return getInstanceVariable("@indefinite_length").isTrue(); + } + + boolean isEOC(final ThreadContext context) { + return getTag(context) == 0 && isUniversal((context)); + } + + boolean isUniversal(final ThreadContext context) { + return getTagClass(context) == BERTags.UNIVERSAL; + } + + IRubyObject tagging() { + return getInstanceVariable("@tagging"); + } + + IRubyObject tagClass() { + return getInstanceVariable("@tag_class"); + } boolean isExplicitTagging() { return ! isImplicitTagging(); } boolean isImplicitTagging() { return true; } int getTag(final ThreadContext context) { - return RubyNumeric.fix2int(callMethod(context, "tag")); + return RubyNumeric.fix2int(getInstanceVariable("@tag")); + } + + int getTagClass(final ThreadContext context) { + IRubyObject tag_class = getInstanceVariable("@tag_class"); + if (tag_class instanceof RubySymbol) { + switch (((RubySymbol) tag_class).asJavaString()) { + case "PRIVATE": + return BERTags.PRIVATE; + case "APPLICATION": + return BERTags.APPLICATION; + case "CONTEXT_SPECIFIC": + return BERTags.CONTEXT_SPECIFIC; + default: // fallback to BERTags.UNIVERSAL + } + } + return BERTags.UNIVERSAL; // 0 } ASN1Encodable toASN1(final ThreadContext context) { @@ -1341,27 +1395,56 @@ ASN1Encodable toASN1(final ThreadContext context) { final ASN1TaggedObject toASN1TaggedObject(final ThreadContext context) { final int tag = getTag(context); + final int tagClass = getTagClass(context); + + final IRubyObject value = callMethod(context, "value"); + if (value instanceof RubyArray) { + // Cruby openssl joins elements of array and casts to string + final RubyArray arr = (RubyArray) value; + + StringBuilder values = new StringBuilder(); + ASN1EncodableVector vec = new ASN1EncodableVector(); - final IRubyObject val = callMethod(context, "value"); - if ( val instanceof RubyArray ) { - final RubyArray arr = (RubyArray) val; - if ( arr.size() > 1 ) { - ASN1EncodableVector vec = new ASN1EncodableVector(); - for ( final IRubyObject obj : arr.toJavaArray() ) { + for (final IRubyObject obj : arr.toJavaArray()) { + if (obj instanceof ASN1Data) { ASN1Encodable data = ((ASN1Data) obj).toASN1(context); - if ( data == null ) break; vec.add( data ); + if (data == null) break; + vec.add(data); + } else { + final IRubyObject string = obj.checkStringType(); + if (string instanceof RubyString) { + values.append(string.asJavaString()); + } else { + throw context.runtime.newTypeError( + "no implicit conversion of " + obj.getMetaClass().getBaseName() + " into String"); + } } - return new DERTaggedObject(isExplicitTagging(), tag, new DERSequence(vec)); } - else if ( arr.size() == 1 ) { - ASN1Encodable data = ((ASN1Data) arr.entry(0)).toASN1(context); - return new DERTaggedObject(isExplicitTagging(), tag, data); + + if (values.length() > 0) { + return new DERTaggedObject(isExplicitTagging(), tagClass, tag, new DERGeneralString(values.toString())); + } else { + // array of strings as value (default) + return new DERTaggedObject(isExplicitTagging(), tagClass, tag, new BERSequence(vec)); } - else { - throw new IllegalStateException("empty array detected"); + } else if (value instanceof ASN1Data) { + return new DERTaggedObject(isExplicitTagging(), tagClass, tag, ((ASN1Data) value).toASN1(context)); + } else if (value instanceof RubyObject) { + if (isEOC(context)) { + return null; + } + final IRubyObject string = value.checkStringType(); + if (string instanceof RubyString) { + return new DERTaggedObject(isExplicitTagging(), tagClass, tag, + new DERGeneralString(string.asJavaString())); + } else { + throw context.runtime.newTypeError( + "no implicit conversion of " + value.getMetaClass().getBaseName() + " into String"); } + } else { + throw context.runtime.newTypeError( + "no implicit conversion of " + value.getMetaClass().getBaseName() + " into String"); } - return new DERTaggedObject(isExplicitTagging(), tag, ((ASN1Data) val).toASN1(context)); } @JRubyMethod @@ -1376,7 +1459,147 @@ public IRubyObject to_der(final ThreadContext context) { } byte[] toDER(final ThreadContext context) throws IOException { - return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER); + if ( + ("ASN1Data".equals(getClassBaseName()) && isUniversal(context)) + ) { + return toDERInternal(context, isConstructive(), isInfiniteLength(), value(context)); + } + + final ASN1Primitive prim = toASN1(context).toASN1Primitive(); + + if (isInfiniteLength()) { + final java.io.ByteArrayOutputStream tagOut = new ByteArrayOutputStream(); + final java.io.ByteArrayOutputStream contentOut = new ByteArrayOutputStream(); + final java.io.ByteArrayOutputStream out = new ByteArrayOutputStream(); + prim.encodeTo(contentOut, ASN1Encoding.DER); + writeDERIdentifier(getTag(context), getTagClass(context) | BERTags.CONSTRUCTED, tagOut); + + byte[] tagOutArr = tagOut.toByteArray(); + byte[] contentOutArr = contentOut.toByteArray(); + + out.write(tagOutArr); + out.write(0x80); + out.write(contentOutArr, tagOutArr.length + 1, contentOutArr.length - tagOutArr.length - 1); + out.write(0x00); + out.write(0x00); + + return out.toByteArray(); + } else { + return prim.getEncoded(ASN1Encoding.DER); + } + } + + byte[] toDERInternal(final ThreadContext context, boolean isConstructed, boolean isIndefiniteLength, final IRubyObject value) throws IOException { + // handstitch conversion + final java.io.ByteArrayOutputStream out = new ByteArrayOutputStream(); + + final byte[] valueBytes; + + if (value == null) { + valueBytes = new byte[] {}; + } else if (value instanceof RubyArray) { + final IRubyObject[] arr = ((RubyArray) value).toJavaArray(); + final java.io.ByteArrayOutputStream valueOut = new ByteArrayOutputStream(); + + + for ( int i = 0; i < arr.length; i++ ) { + final IRubyObject obj = arr[i]; + + if (obj instanceof EndOfContent && i != arr.length - 1) { + throw newASN1Error(context.runtime, "illegal EOC octets in value"); + } + + final byte[] objBytes; + + if (obj.respondsTo("to_der")) { + objBytes = ((RubyString) obj.callMethod(context, "to_der")).getBytes(); + } else { + objBytes = ((RubyString) obj.convertToString()).getBytes(); + } + + valueOut.write(objBytes); + } + + if (isIndefiniteLength) { + if (arr.length != 0 && !(arr[arr.length - 1] instanceof EndOfContent)) { + // indefinite length object with no EOC object in the array. + valueOut.write(0x00); + valueOut.write(0x00); + } + } + + valueBytes = valueOut.toByteArray(); + } else { + if (isIndefiniteLength) { + throw newASN1Error( + context.runtime, + "indefinite length form cannot be used with primitive encoding" + ); + } + + if (value instanceof RubyString) { + valueBytes = ((RubyString) value).getBytes(); + } else { + valueBytes = value.convertToString().getBytes(); + } + } + + int flags = getTagClass(context); + if (isConstructed) { + flags |= BERTags.CONSTRUCTED; + } + // tag + writeDERIdentifier(getTag(context), flags, out); + if (isIndefiniteLength) { + out.write(0x80); + } else { + writeDERLength(valueBytes.length, out); + } + // value + out.write(valueBytes); + + return out.toByteArray(); + } + + void writeDERIdentifier(int tag, int flags, java.io.ByteArrayOutputStream out) { + if (tag > 0x1f) { + byte[] stack = new byte[6]; + int pos = stack.length; + + stack[--pos] = (byte)(tag & 0x7F); + while (tag > 127) + { + tag >>>= 7; + stack[--pos] = (byte)(tag & 0x7F | 0x80); + } + + stack[--pos] = (byte)(flags | 0x1F); + + out.write(stack, pos, stack.length - pos); + } else { + out.write(flags | tag); + } + } + + void writeDERLength(int length, java.io.ByteArrayOutputStream out) { + if (length < 128) { + out.write(length); + } else { + byte[] stack = new byte[5]; + int pos = stack.length; + + do + { + stack[--pos] = (byte)length; + length >>>= 8; + } + while (length != 0); + + int count = stack.length - pos; + stack[--pos] = (byte)(0x80 | count); + + out.write(stack, pos, count - pos); + } } protected IRubyObject defaultTag() { @@ -1426,12 +1649,46 @@ static void printArray(final PrintStream out, final int indent, final RubyArray } } - static RaiseException createNativeRaiseException(final ThreadContext context, - final Throwable e) { - Throwable cause = e.getCause(); if ( cause == null ) cause = e; - return RaiseException.createNativeRaiseException(context.runtime, cause); + } + + public static class EndOfContent extends ASN1Data { + + static ObjectAllocator ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klass) { + return new EndOfContent(runtime, klass); + } + }; + + public EndOfContent(Ruby runtime, RubyClass type) { + super(runtime,type); } + + @JRubyMethod(required = 0, optional = 0, visibility = Visibility.PRIVATE) + public static IRubyObject initialize(final ThreadContext context, final IRubyObject self) { + final Ruby runtime = context.runtime; + self.getInstanceVariables().setInstanceVariable("@tag", runtime.newFixnum(0)); + self.getInstanceVariables().setInstanceVariable("@value", RubyString.newEmptyString(context.runtime)); + self.getInstanceVariables().setInstanceVariable("@tag_class", runtime.newSymbol("UNIVERSAL")); + return self; + } + + static IRubyObject newInstance(final ThreadContext context) { + RubyClass klass = _ASN1(context.runtime).getClass("EndOfContent"); + return klass.newInstance(context, Block.NULL_BLOCK); + } + + @Override + boolean isImplicitTagging() { + IRubyObject tagging = tagging(); + if ( tagging.isNil() ) return true; + return "IMPLICIT".equals( tagging.toString() ); + } + + @Override + byte[] toDER(final ThreadContext context) throws IOException { + return toDERInternal(context, false, false, null); + } } public static class Primitive extends ASN1Data { @@ -1450,7 +1707,7 @@ public Primitive(Ruby runtime, RubyClass type) { @Override @JRubyMethod public IRubyObject to_der(final ThreadContext context) { - if ( value(context).isNil() ) { + if ( value(context).isNil() && !isNull() ) { // MRI compatibility but avoids Java exceptions as well e.g. // Java::JavaLang::NumberFormatException // java.math.BigInteger.(BigInteger.java:296) @@ -1489,12 +1746,20 @@ static void initializeImpl(final ThreadContext context, if ( tag.isNil() ) throw newASN1Error(runtime, "must specify tag number"); - if ( tagging.isNil() ) tagging = runtime.newSymbol("EXPLICIT"); - if ( ! (tagging instanceof RubySymbol) ) { - throw newASN1Error(runtime, "invalid tag default"); + if ( tagging.isNil()) { + if (tag_class.isNil()) { + tag_class = runtime.newSymbol("UNIVERSAL"); + } + } else { + if (!(tagging instanceof RubySymbol)) { + throw newASN1Error(runtime, "invalid tagging method"); + } + + if (tag_class.isNil()) { + tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); + } } - if ( tag_class.isNil() ) tag_class = runtime.newSymbol("CONTEXT_SPECIFIC"); if ( ! (tag_class instanceof RubySymbol) ) { throw newASN1Error(runtime, "invalid tag class"); } @@ -1510,84 +1775,86 @@ static void initializeImpl(final ThreadContext context, // NOTE: Primitive only final String baseName = self.getMetaClass().getRealClass().getBaseName(); - if ( "ObjectId".equals( baseName ) ) { - final String name; - try { - name = oid2Sym( runtime, getObjectID(runtime, value.toString()), true ); - } - catch (IllegalArgumentException e) { - // e.g. in case of nil "string not an OID" - throw runtime.newTypeError(e.getMessage()); - } - if ( name != null ) value = runtime.newString(name); + switch (baseName) { + case "ObjectId": + final String name; + try { + name = oid2Sym( runtime, getObjectID(runtime, value.toString()), true ); + } + catch (IllegalArgumentException e) { + // e.g. in case of nil "string not an OID" + throw runtime.newTypeError(e.getMessage()); + } + if ( name != null ) value = runtime.newString(name); + break; + case "BitString": + self.setInstanceVariable("@unused_bits", runtime.newFixnum(0)); + break; } self.setInstanceVariable("@tag", tag); self.setInstanceVariable("@value", value); self.setInstanceVariable("@tag_class", tag_class); self.setInstanceVariable("@tagging", tagging); - self.setInstanceVariable("@infinite_length", runtime.getFalse()); + self.setInstanceVariable("@indefinite_length", runtime.getFalse()); + } + + boolean isTagged() { + return !tagging().isNil(); } @Override boolean isExplicitTagging() { - return "EXPLICIT".equals( getInstanceVariable("@tagging").toString() ); + return "EXPLICIT".equals( tagging().toString() ); } @Override boolean isImplicitTagging() { - IRubyObject tagging = getInstanceVariable("@tagging"); + IRubyObject tagging = tagging(); if ( tagging.isNil() ) return true; return "IMPLICIT".equals( tagging.toString() ); } @Override - boolean isEOC() { - return "EndOfContent".equals( getClassBaseName() ); + boolean isEOC(final ThreadContext context) { + return false; + } + + private boolean isNull() { + return "Null".equals(getMetaClass().getRealClass().getBaseName()); } @Override byte[] toDER(final ThreadContext context) throws IOException { - if ( isEOC() ) return new byte[] { 0x00, 0x00 }; - return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER); - } + Class type = typeClass( getMetaClass() ); + final IRubyObject value = value(context); - static Primitive newInstance(final ThreadContext context, final String type, - final IRubyObject value) { - RubyClass klass = _ASN1(context.runtime).getClass(type); - final Primitive self = new Primitive(context.runtime, klass); - if ( value != null ) self.setInstanceVariable("@value", value); - return self; - } + if ( type == null ) { + RubyString string; - static Primitive newEndOfContent(final ThreadContext context) { - return newInstance(context, "EndOfContent", null); - } + if (value instanceof RubyString) { + string = (RubyString) value; + } else { + string = value.convertToString(); + } - /* - private static final Class DERBooleanClass; - static { - Class klass; - try { - klass = Class.forName("org.bouncycastle.asn1.DERBoolean"); + return toDERInternal(context, false, false, string); } - catch(ClassNotFoundException e) { klass = null; } - DERBooleanClass = klass; - } */ + + return toASN1(context).toASN1Primitive().getEncoded(ASN1Encoding.DER); + } @Override ASN1Encodable toASN1(final ThreadContext context) { - Class type = typeClass( getMetaClass() ); - if ( type == null ) { - final int tag = getTag(context); - if ( tag == 0 ) return null; // TODO pass EOC to BC ? - if ( isExplicitTagging() ) type = typeClass( tag ); - if ( type == null ) { - throw new IllegalArgumentException( - "no type for: " + getMetaClass() + " or tag: " + getTag(context) - ); - } + final ASN1Encodable primitive = toASN1Primitive(context); + if (isTagged()) { + return new DERTaggedObject(isExplicitTagging(), getTagClass(context), getTag(context), primitive); } + return primitive; + } + + private ASN1Encodable toASN1Primitive(final ThreadContext context) { + Class type = typeClass( getMetaClass() ); final IRubyObject val = value(context); if ( type == ASN1ObjectIdentifier.class ) { @@ -1621,22 +1888,17 @@ ASN1Encodable toASN1(final ThreadContext context) { return new DEROctetString( val.asString().getBytes() ); } if ( type == DERBitString.class ) { - final byte[] bs = val.asString().getBytes(); - int unused = 0; - for ( int i = (bs.length - 1); i > -1; i-- ) { - if (bs[i] == 0) unused += 8; - else { - byte v2 = bs[i]; - int x = 8; - while ( v2 != 0 ) { - v2 <<= 1; - x--; - } - unused += x; - break; - } + final byte[] data = val.asString().getBytes(); + int padBits = 0; + IRubyObject unused_bits = getInstanceVariable("@unused_bits"); + if (unused_bits != null) { + padBits = unused_bits.convertToInteger("to_i").getIntValue(); + } + try { + return new DERBitString(data, padBits); + } catch (IllegalArgumentException e) { + throw newASN1Error(context.runtime, e.getMessage()); } - return new DERBitString(bs, unused); } if ( type == DERIA5String.class ) { return new DERIA5String( val.asString().toString() ); @@ -1645,45 +1907,57 @@ ASN1Encodable toASN1(final ThreadContext context) { return new DERUTF8String( val.asString().toString() ); } if ( type == DERBMPString.class ) { - return new DERBMPString( val.asString().toString() ); + return new DERBMPString(new String(toBMPChars(val.asString().getByteList()))); } if ( type == DERUniversalString.class ) { return new DERUniversalString( val.asString().getBytes() ); } if ( type == DERGeneralString.class ) { - return DERGeneralString.getInstance( val.asString().getBytes() ); + return new DERGeneralString( val.asString().toString() ); } if ( type == DERVisibleString.class ) { - return DERVisibleString.getInstance( val.asString().getBytes() ); + return new DERVisibleString( val.asString().toString() ); } if ( type == DERNumericString.class ) { - return DERNumericString.getInstance( val.asString().getBytes() ); + return new DERNumericString( val.asString().toString() ); + } + if ( type == DERPrintableString.class ) { + return new DERPrintableString( val.asString().toString() ); + } + if ( type == DERT61String.class ) { + return new DERT61String( val.asString().toString() ); + } + if ( type == DERVideotexString.class ) { + return new DERVideotexString( val.asString().getBytes() ); + } + if ( type == DERGraphicString.class ) { + return new DERGraphicString( val.asString().getBytes() ); } - if ( val instanceof RubyString ) { - try { - return typeInstance(type, ( (RubyString) val ).getBytes()); - } - catch (Exception e) { // TODO exception handling - debugStackTrace(context.runtime, e); - throw createNativeRaiseException(context, e); - } + if (isDebug(context.runtime)) { + debug(this + " toASN1() could not handle class " + getMetaClass() + " and value: " + val.inspect() + " (" + val.getMetaClass() + ")"); } + throw new UnsupportedOperationException("OpenSSL::ASN1Data#toASN1 (" + type + ") not implemented"); // should not happen + } - // TODO throw an exception here too? - if ( isDebug(context.runtime) ) { - debug(this + " toASN1() could not handle class " + getMetaClass() + " and value " + val.inspect() + " (" + val.getClass().getName() + ")"); + private static char[] toBMPChars(final ByteList string) { + assert string.length() % 2 == 0; + + final int len = string.length() / 2; + final char[] chars = new char[len]; + for (int i = 0; i < len; i++) { + int si = i * 2; + chars[i] = (char)((string.get(si) << 8) | (string.get(si + 1) & 0xff)); } - warn(context, "WARNING: unimplemented method called: OpenSSL::ASN1Data#toASN1 (" + type + ")"); - return null; + return chars; } private static BigInteger bigIntegerValue(final IRubyObject val) { if ( val instanceof RubyInteger ) { // RubyBignum return ((RubyInteger) val).getBigIntegerValue(); } - if ( val instanceof BN ) ((BN) val).getValue(); + if ( val instanceof BN ) return ((BN) val).getValue(); return new BigInteger( val.asString().getBytes() ); } @@ -1717,33 +1991,15 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a return this; } - static Constructive newInfiniteConstructive(final ThreadContext context, - final String type, final List value, final int defaultTag) { + static Constructive newInfiniteLength(final ThreadContext context, + final IRubyObject value, final int defaultTag) { final Ruby runtime = context.runtime; - final RubyClass klass = _ASN1(context.runtime).getClass(type); + final RubyClass klass = _ASN1(context.runtime).getClass("Constructive"); final Constructive self = new Constructive(runtime, klass); - final RubyArray values = runtime.newArray(value.size()); - for ( final IRubyObject val : value ) values.append(val); - // values.append( Primitive.newEndOfContent(context) ); - - self.setInstanceVariable("@tag", runtime.newFixnum(defaultTag)); - self.setInstanceVariable("@value", values); - self.setInstanceVariable("@tag_class", runtime.newSymbol("UNIVERSAL")); - self.setInstanceVariable("@tagging", context.nil); - - return setInfiniteLength(context, self); - } - - static Constructive setInfiniteLength(final ThreadContext context, final IRubyObject constructive) { - final Constructive instance = ((Constructive) constructive); - final IRubyObject eoc = Primitive.newEndOfContent(context); - final IRubyObject value = instance.value(context); - if ( value instanceof RubyArray ) ((RubyArray) value).append(eoc); - else value.callMethod(context, "<<", eoc); - instance.setInstanceVariable("@infinite_length", context.runtime.getTrue()); - return instance; + ASN1Data.newInfiniteLengthImpl(context, self, value, defaultTag, runtime.newSymbol("UNIVERSAL")); + return self; } private boolean rawConstructive() { @@ -1758,20 +2014,18 @@ private boolean isSet() { return "Set".equals( getClassBaseName() ); } - private boolean isInfiniteLength() { - return getInstanceVariable("@infinite_length").isTrue(); + private boolean isTagged() { + return !tagging().isNil(); } @Override boolean isExplicitTagging() { - IRubyObject tagging = getInstanceVariable("@tagging"); - if ( tagging.isNil() ) return true; - return "EXPLICIT".equals( tagging.toString() ); + return "EXPLICIT".equals( tagging().toString() ); // nil.toString() == "" } @Override boolean isImplicitTagging() { - return "IMPLICIT".equals( getInstanceVariable("@tagging").toString() ); + return "IMPLICIT".equals( tagging().toString() ); } @Override @@ -1805,38 +2059,39 @@ ASN1Encodable toASN1(final ThreadContext context) { @Override @JRubyMethod public IRubyObject to_der(final ThreadContext context) { - if ( rawConstructive() ) { // MRI compatibility - if ( ! isInfiniteLength() && ! super.value(context).isNil() ) { - final Ruby runtime = context.runtime; - throw newASN1Error(runtime, "Constructive shall only be used" - + " with infinite length"); - } - } return super.to_der(context); } @Override byte[] toDER(final ThreadContext context) throws IOException { - if ( isInfiniteLength() ) { - if ( isSequence() ) { + final int tagNo = getTag(context); + final boolean isIndefiniteLength = isInfiniteLength(); + + if ( isIndefiniteLength ) { + if ( isSequence() || tagNo == SEQUENCE ) { return sequenceToDER(context); } - else if ( isSet() ) { + if ( isSet() || tagNo == SET) { return setToDER(context); } - else { // "raw" Constructive - switch ( getTag(context) ) { - case OCTET_STRING: - return octetStringToDER(context); - case BIT_STRING: - return bitStringToDER(context); - case SEQUENCE: - return sequenceToDER(context); - case SET: - return setToDER(context); - } - throw new UnsupportedOperationException( this.inspect().toString() ); + // "raw" Constructive + switch ( getTag(context) ) { + case OCTET_STRING: + return octetStringToDER(context); + case BIT_STRING: + return bitStringToDER(context); } + return toDERInternal(context, true, isInfiniteLength(), value(context)); + } + + if (isEOC(context)) { + return toDERInternal(context, true, isIndefiniteLength, null); + } + + Class type = typeClass( getMetaClass() ); + + if ( type == null ) { + return toDERInternal(context, true, isIndefiniteLength, valueAsArray(context)); } return super.toDER(context); @@ -1888,21 +2143,23 @@ private byte[] setToDER(final ThreadContext context) throws IOException { private ASN1EncodableVector toASN1EncodableVector(final ThreadContext context) { final ASN1EncodableVector vec = new ASN1EncodableVector(); final IRubyObject value = value(context); - if ( value instanceof RubyArray ) { - final RubyArray val = (RubyArray) value; - for ( int i = 0; i < val.size(); i++ ) { - if ( addEntry(context, vec, val.entry(i)) ) break; - } + final RubyArray val = valueAsArray(context); + for ( int i = 0; i < val.size(); i++ ) { + if ( addEntry(context, vec, val.entry(i)) ) break; } - else { - final int size = RubyInteger.num2int(value.callMethod(context, "size")); - for ( int i = 0; i < size; i++ ) { - final RubyInteger idx = context.runtime.newFixnum(i); - IRubyObject entry = value.callMethod(context, "[]", idx); - if ( addEntry(context, vec, entry) ) break; + return vec; + } + + private RubyArray valueAsArray(final ThreadContext context) { + final IRubyObject value = value(context); + if (value instanceof RubyArray ) { + return (RubyArray) value; + } else { + if (!value.respondsTo("to_a")) { + throw context.runtime.newTypeError("can't convert " + value.getMetaClass().getName() + " into Array"); } + return (RubyArray) value.callMethod(context, "to_a"); } - return vec; } public ASN1Primitive toASN1Primitive() { @@ -1922,8 +2179,7 @@ public ASN1Primitive toASN1Primitive() { } - private static boolean addEntry(final ThreadContext context, - final ASN1EncodableVector vec, final IRubyObject entry) { + private static boolean addEntry(final ThreadContext context, final ASN1EncodableVector vec, final IRubyObject entry) { try { if ( entry instanceof Constructive ) { final Constructive constructive = (Constructive) entry; @@ -1936,7 +2192,7 @@ private static boolean addEntry(final ThreadContext context, } else if ( entry instanceof ASN1Data ) { final ASN1Data data = ( (ASN1Data) entry ); - if ( data.isEOC() ) return true; + if ( data.isEOC(context) ) return true; vec.add( data.toASN1(context) ); } else { diff --git a/src/main/java/org/jruby/ext/openssl/BN.java b/src/main/java/org/jruby/ext/openssl/BN.java index 375f5f0c..1e61d463 100644 --- a/src/main/java/org/jruby/ext/openssl/BN.java +++ b/src/main/java/org/jruby/ext/openssl/BN.java @@ -46,7 +46,6 @@ import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; @@ -77,10 +76,6 @@ public class BN extends RubyObject { private static final int DEFAULT_CERTAINTY = 100; - private static final ObjectAllocator BN_ALLOCATOR = new ObjectAllocator() { - public BN allocate(Ruby runtime, RubyClass klass) { return new BN(runtime, klass); } - }; - public static BN newBN(Ruby runtime, BigInteger value) { return newInstance(runtime, value); } @@ -92,7 +87,7 @@ static BN newInstance(final Ruby runtime, BigInteger value) { static void createBN(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { OpenSSL.defineClassUnder("BNError", OpenSSLError, OpenSSLError.getAllocator()); - RubyClass BN = OpenSSL.defineClassUnder("BN", runtime.getObject(), BN_ALLOCATOR); + RubyClass BN = OpenSSL.defineClassUnder("BN", runtime.getObject(), (r, klass) -> new BN(r, klass)); BN.includeModule( runtime.getModule("Comparable") ); BN.defineAnnotatedMethods(BN.class); } @@ -176,6 +171,7 @@ private static BigInteger initBigIntegerBase(final Ruby runtime, RubyString str, } } + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject that) { super.initialize_copy(that); @@ -297,8 +293,8 @@ public RubyInteger hash(final ThreadContext context) { return context.runtime.newFixnum(hashCode()); } - @JRubyMethod(name = "to_i") - public RubyInteger to_i() { + @JRubyMethod(name = "to_int", alias = "to_i") + public RubyInteger to_int() { if ( value.compareTo( MAX_LONG ) > 0 || value.compareTo( MIN_LONG ) < 0 ) { return RubyBignum.newBignum(getRuntime(), value); } @@ -311,7 +307,6 @@ public BN to_bn() { } @JRubyMethod(name="coerce") - // FIXME: is this right? don't see how it would be useful... public IRubyObject coerce(IRubyObject other) { final Ruby runtime = getRuntime(); IRubyObject self; @@ -319,7 +314,7 @@ public IRubyObject coerce(IRubyObject other) { self = runtime.newString(value.toString()); } else if ( other instanceof RubyInteger ) { - self = to_i(); + self = to_int(); } else if ( other instanceof BN ) { self = this; @@ -330,6 +325,26 @@ else if ( other instanceof BN ) { return runtime.newArray(other, self); } + @JRubyMethod(name="-@") + public IRubyObject op_uminus(final ThreadContext context) { + return newBN(context.runtime, value.negate()); + } + + @JRubyMethod(name="+@") + public IRubyObject op_uplus(final ThreadContext context) { + return newBN(context.runtime, value); + } + + @JRubyMethod + public IRubyObject abs(final ThreadContext context) { + return newBN(context.runtime, value.abs()); + } + + @JRubyMethod(name="negative?") + public IRubyObject negative_p(final ThreadContext context) { + return context.runtime.newBoolean(value.signum() == -1); + } + @JRubyMethod(name="zero?") public RubyBoolean zero_p(final ThreadContext context) { return context.runtime.newBoolean( value.equals(BigInteger.ZERO) ); @@ -909,7 +924,7 @@ public static BigInteger getBigInteger(final IRubyObject arg) { @Override public Object toJava(Class target) { - if ( target.isAssignableFrom(BigInteger.class) || target == Number.class ) return value; + if ( target.isAssignableFrom(BigInteger.class) ) return value; if ( target == Long.class || target == Long.TYPE ) return value.longValue(); if ( target == Integer.class || target == Integer.TYPE ) return value.intValue(); if ( target == Double.class || target == Double.TYPE ) return value.doubleValue(); diff --git a/src/main/java/org/jruby/ext/openssl/Cipher.java b/src/main/java/org/jruby/ext/openssl/Cipher.java index 92239715..af911304 100644 --- a/src/main/java/org/jruby/ext/openssl/Cipher.java +++ b/src/main/java/org/jruby/ext/openssl/Cipher.java @@ -42,9 +42,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import static javax.crypto.Cipher.DECRYPT_MODE; -import static javax.crypto.Cipher.ENCRYPT_MODE; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; @@ -70,6 +67,8 @@ import org.jruby.util.ByteList; import org.jruby.util.TypeConverter; +import static javax.crypto.Cipher.DECRYPT_MODE; +import static javax.crypto.Cipher.ENCRYPT_MODE; import static org.jruby.ext.openssl.OpenSSL.*; /** @@ -78,12 +77,8 @@ public class Cipher extends RubyObject { private static final long serialVersionUID = -5390983669951165103L; - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public Cipher allocate(Ruby runtime, RubyClass klass) { return new Cipher(runtime, klass); } - }; - static void createCipher(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { - final RubyClass Cipher = OpenSSL.defineClassUnder("Cipher", runtime.getObject(), ALLOCATOR); + final RubyClass Cipher = OpenSSL.defineClassUnder("Cipher", runtime.getObject(), (r, klass) -> new Cipher(r, klass)); Cipher.defineClassUnder("CipherError", OpenSSLError, OpenSSLError.getAllocator()); Cipher.defineAnnotatedMethods(Cipher.class); @@ -142,7 +137,7 @@ static RubyClass _Cipher(final Ruby runtime) { public static IRubyObject ciphers(final ThreadContext context, final IRubyObject self) { final Ruby runtime = context.runtime; - final Collection ciphers = Algorithm.allSupportedCiphers().keySet(); + final Collection ciphers = Algorithm.AllSupportedCiphers.CIPHERS_MAP.keySet(); final RubyArray result = runtime.newArray( ciphers.size() * 2 ); for ( final String cipher : ciphers ) { result.append( runtime.newString(cipher) ); // upper-case @@ -156,7 +151,7 @@ public static IRubyObject ciphers(final ThreadContext context, final IRubyObject public static boolean isSupportedCipher(final String name) { final String osslName = name.toUpperCase(); // - if ( Algorithm.allSupportedCiphers().get( osslName ) != null ) { + if ( Algorithm.AllSupportedCiphers.CIPHERS_MAP.get( osslName ) != null ) { return true; } // @@ -273,125 +268,115 @@ public static final class Algorithm { NO_PADDING_BLOCK_MODES.add("GCM"); } - // Ruby to Java name String (or FALSE) - static final HashMap supportedCiphers = new LinkedHashMap(120, 1); - // we're _marking_ unsupported keys with a Boolean.FALSE mapping - // all cipher mappings are resolved on `OpenSSL::Cipher.ciphers` - // ... probably a slightly _rare_ case to be going for up front - static boolean supportedCiphersAll; - - static Map allSupportedCiphers() { - if ( supportedCiphersAll ) return supportedCiphers; - synchronized ( supportedCiphers ) { - if ( supportedCiphersAll ) return supportedCiphers; + final static class AllSupportedCiphers { + // Ruby to Java name String + static final HashMap CIPHERS_MAP = new LinkedHashMap(120, 1); + static { // cleanup all FALSE keys : //for ( String key: supportedCiphers.keySet() ) { - //if ( supportedCiphers.get(key) == Boolean.FALSE ) supportedCiphers.remove(key); + //if ( supportedCiphers.get(key) == Boolean.FALSE ) supportedCiphers.remove(key); //} // OpenSSL: all the block ciphers normally use PKCS#5 padding String[] modes; modes = cipherModes("AES"); // null if not supported - if ( modes != null ) { - for ( final String mode : modes ) { + if (modes != null) { + for (final String mode : modes) { final String realName = "AES/" + mode; // + "/PKCS5Padding" - supportedCiphers.put( "AES-128-" + mode, new String[] { "AES", mode, "128", realName } ); - supportedCiphers.put( "AES-192-" + mode, new String[] { "AES", mode, "192", realName } ); - supportedCiphers.put( "AES-256-" + mode, new String[] { "AES", mode, "256", realName } ); + CIPHERS_MAP.put("AES-128-" + mode, new String[]{"AES", mode, "128", realName}); + CIPHERS_MAP.put("AES-192-" + mode, new String[]{"AES", mode, "192", realName}); + CIPHERS_MAP.put("AES-256-" + mode, new String[]{"AES", mode, "256", realName}); } final String realName = "AES/CBC"; - supportedCiphers.put( "AES128", new String[] { "AES", "CBC", "128", realName } ); - supportedCiphers.put( "AES192", new String[] { "AES", "CBC", "192", realName } ); - supportedCiphers.put( "AES256", new String[] { "AES", "CBC", "256", realName } ); + CIPHERS_MAP.put("AES128", new String[]{"AES", "CBC", "128", realName}); + CIPHERS_MAP.put("AES192", new String[]{"AES", "CBC", "192", realName}); + CIPHERS_MAP.put("AES256", new String[]{"AES", "CBC", "256", realName}); } modes = cipherModes("Blowfish"); - if ( modes != null ) { - supportedCiphers.put( "BF", new String[] { "BF", "CBC", null, "Blowfish/CBC" }); - for ( final String mode : modes ) { - supportedCiphers.put( "BF-" + mode, new String[] { "BF", mode, null, "Blowfish/" + mode } ); + if (modes != null) { + CIPHERS_MAP.put("BF", new String[]{"BF", "CBC", null, "Blowfish/CBC"}); + for (final String mode : modes) { + CIPHERS_MAP.put("BF-" + mode, new String[]{"BF", mode, null, "Blowfish/" + mode}); } } modes = cipherModes("Camellia"); - if ( modes != null ) { - for ( final String mode : modes ) { + if (modes != null) { + for (final String mode : modes) { final String realName = "Camellia/" + mode; - supportedCiphers.put( "CAMELLIA-128-" + mode, new String[] { "CAMELLIA", mode, "128", realName } ); - supportedCiphers.put( "CAMELLIA-192-" + mode, new String[] { "CAMELLIA", mode, "192", realName } ); - supportedCiphers.put( "CAMELLIA-256-" + mode, new String[] { "CAMELLIA", mode, "256", realName } ); + CIPHERS_MAP.put("CAMELLIA-128-" + mode, new String[]{"CAMELLIA", mode, "128", realName}); + CIPHERS_MAP.put("CAMELLIA-192-" + mode, new String[]{"CAMELLIA", mode, "192", realName}); + CIPHERS_MAP.put("CAMELLIA-256-" + mode, new String[]{"CAMELLIA", mode, "256", realName}); } final String realName = "Camellia/CBC"; - supportedCiphers.put( "CAMELLIA128", new String[] { "CAMELLIA", "CBC", "128", realName } ); - supportedCiphers.put( "CAMELLIA192", new String[] { "CAMELLIA", "CBC", "192", realName } ); - supportedCiphers.put( "CAMELLIA256", new String[] { "CAMELLIA", "CBC", "256", realName } ); + CIPHERS_MAP.put("CAMELLIA128", new String[]{"CAMELLIA", "CBC", "128", realName}); + CIPHERS_MAP.put("CAMELLIA192", new String[]{"CAMELLIA", "CBC", "192", realName}); + CIPHERS_MAP.put("CAMELLIA256", new String[]{"CAMELLIA", "CBC", "256", realName}); } modes = cipherModes("CAST5"); - if ( modes != null ) { - supportedCiphers.put( "CAST", new String[] { "CAST", "CBC", null, "CAST5/CBC" } ); - supportedCiphers.put( "CAST-CBC", supportedCiphers.get("CAST") ); - for ( final String mode : modes ) { - supportedCiphers.put( "CAST5-" + mode, new String[] { "CAST", mode, null, "CAST5/" + mode }); + if (modes != null) { + CIPHERS_MAP.put("CAST", new String[]{"CAST", "CBC", null, "CAST5/CBC"}); + CIPHERS_MAP.put("CAST-CBC", CIPHERS_MAP.get("CAST")); + for (final String mode : modes) { + CIPHERS_MAP.put("CAST5-" + mode, new String[]{"CAST", mode, null, "CAST5/" + mode}); } } modes = cipherModes("CAST6"); - if ( modes != null ) { - for ( final String mode : modes ) { - supportedCiphers.put( "CAST6-" + mode, new String[] { "CAST6", mode, null, "CAST6/" + mode }); + if (modes != null) { + for (final String mode : modes) { + CIPHERS_MAP.put("CAST6-" + mode, new String[]{"CAST6", mode, null, "CAST6/" + mode}); } } modes = cipherModes("DES"); - if ( modes != null ) { - supportedCiphers.put( "DES", new String[] { "DES", "CBC", null, "DES/CBC" } ); - for ( final String mode : modes ) { - supportedCiphers.put( "DES-" + mode, new String[] { "DES", mode, null, "DES/" + mode }); + if (modes != null) { + CIPHERS_MAP.put("DES", new String[]{"DES", "CBC", null, "DES/CBC"}); + for (final String mode : modes) { + CIPHERS_MAP.put("DES-" + mode, new String[]{"DES", mode, null, "DES/" + mode}); } } modes = cipherModes("DESede"); - if ( modes != null ) { - supportedCiphers.put( "DES-EDE", new String[] { "DES", "ECB", "EDE", "DESede/ECB" } ); - supportedCiphers.put( "DES-EDE-CBC", new String[] { "DES", "CBC", "EDE", "DESede/CBC" } ); - supportedCiphers.put( "DES-EDE-CFB", new String[] { "DES", "CBC", "EDE", "DESede/CFB" } ); - supportedCiphers.put( "DES-EDE-OFB", new String[] { "DES", "CBC", "EDE", "DESede/OFB" } ); - supportedCiphers.put( "DES-EDE3", new String[] { "DES", "ECB", "EDE3", "DESede/ECB" }); - for ( final String mode : modes ) { - supportedCiphers.put( "DES-EDE3-" + mode, new String[] { "DES", mode, "EDE3", "DESede/" + mode }); + if (modes != null) { + CIPHERS_MAP.put("DES-EDE", new String[]{"DES", "ECB", "EDE", "DESede/ECB"}); + CIPHERS_MAP.put("DES-EDE-CBC", new String[]{"DES", "CBC", "EDE", "DESede/CBC"}); + CIPHERS_MAP.put("DES-EDE-CFB", new String[]{"DES", "CBC", "EDE", "DESede/CFB"}); + CIPHERS_MAP.put("DES-EDE-OFB", new String[]{"DES", "CBC", "EDE", "DESede/OFB"}); + CIPHERS_MAP.put("DES-EDE3", new String[]{"DES", "ECB", "EDE3", "DESede/ECB"}); + for (final String mode : modes) { + CIPHERS_MAP.put("DES-EDE3-" + mode, new String[]{"DES", mode, "EDE3", "DESede/" + mode}); } - supportedCiphers.put( "DES3", new String[] { "DES", "CBC", "EDE3", "DESede/CBC" } ); + CIPHERS_MAP.put("DES3", new String[]{"DES", "CBC", "EDE3", "DESede/CBC"}); } modes = cipherModes("RC2"); - if ( modes != null ) { - supportedCiphers.put( "RC2", new String[] { "RC2", "CBC", null, "RC2/CBC" } ); - for ( final String mode : modes ) { - supportedCiphers.put( "RC2-" + mode, new String[] { "RC2", mode, null, "RC2/" + mode } ); + if (modes != null) { + CIPHERS_MAP.put("RC2", new String[]{"RC2", "CBC", null, "RC2/CBC"}); + for (final String mode : modes) { + CIPHERS_MAP.put("RC2-" + mode, new String[]{"RC2", mode, null, "RC2/" + mode}); } - supportedCiphers.put( "RC2-40-CBC", new String[] { "RC2", "CBC", "40", "RC2/CBC" } ); - supportedCiphers.put( "RC2-64-CBC", new String[] { "RC2", "CBC", "64", "RC2/CBC" } ); + CIPHERS_MAP.put("RC2-40-CBC", new String[]{"RC2", "CBC", "40", "RC2/CBC"}); + CIPHERS_MAP.put("RC2-64-CBC", new String[]{"RC2", "CBC", "64", "RC2/CBC"}); } modes = cipherModes("RC4"); // NOTE: stream cipher (BC supported) - if ( modes != null ) { - supportedCiphers.put( "RC4", new String[] { "RC4", null, null, "RC4" } ); - supportedCiphers.put( "RC4-40", new String[] { "RC4", null, "40", "RC4" } ); + if (modes != null) { + CIPHERS_MAP.put("RC4", new String[]{"RC4", null, null, "RC4"}); + CIPHERS_MAP.put("RC4-40", new String[]{"RC4", null, "40", "RC4"}); //supportedCiphers.put( "RC2-HMAC-MD5", new String[] { "RC4", null, null, "RC4" }); } modes = cipherModes("SEED"); - if ( modes != null ) { - supportedCiphers.put( "SEED", new String[] { "SEED", "CBC", null, "SEED/CBC" } ); - for ( final String mode : modes ) { - supportedCiphers.put( "SEED-" + mode, new String[] { "SEED", mode, null, "SEED/" + mode }); + if (modes != null) { + CIPHERS_MAP.put("SEED", new String[]{"SEED", "CBC", null, "SEED/CBC"}); + for (final String mode : modes) { + CIPHERS_MAP.put("SEED-" + mode, new String[]{"SEED", mode, null, "SEED/" + mode}); } } - - supportedCiphersAll = true; - return supportedCiphers; } } @@ -436,7 +421,7 @@ static Algorithm osslToJava(final String osslName) { private static Algorithm osslToJava(final String osslName, final String padding) { - final String[] algVals = supportedCiphers.get(osslName); + final String[] algVals = AllSupportedCiphers.CIPHERS_MAP.get(osslName); if ( algVals != null ) { final String cryptoMode = algVals[1]; Algorithm alg = new Algorithm(algVals[0], algVals[2], cryptoMode); @@ -730,7 +715,6 @@ public Cipher(Ruby runtime, RubyClass type) { private int generateKeyLength = -1; private int ivLength = -1; private boolean encryptMode = true; - //private IRubyObject[] modeParams; private boolean cipherInited = false; private byte[] key; private byte[] realIV; @@ -825,6 +809,12 @@ public final RubyInteger iv_len() { return getRuntime().newFixnum(ivLength); } + @JRubyMethod(name = "iv_len=", required = 1) + public final IRubyObject set_iv_len(IRubyObject len) { + this.ivLength = RubyNumeric.fix2int(len); + return len; + } + @JRubyMethod(name = "key_len=", required = 1) public final IRubyObject set_key_len(IRubyObject len) { this.keyLength = RubyNumeric.fix2int(len); @@ -1274,11 +1264,24 @@ public IRubyObject set_padding(IRubyObject padding) { } private transient ByteList auth_tag; + private int auth_tag_len = 16; @JRubyMethod(name = "auth_tag") public IRubyObject auth_tag(final ThreadContext context) { + return getAuthTag(context, auth_tag_len); + } + + @JRubyMethod(name = "auth_tag") + public IRubyObject auth_tag(final ThreadContext context, IRubyObject tag_len) { + return getAuthTag(context, tag_len.convertToInteger().getIntValue()); + } + + private IRubyObject getAuthTag(final ThreadContext context, final int tag_len) { if ( auth_tag != null ) { - return RubyString.newString(context.runtime, auth_tag); + if (auth_tag.length() <= tag_len) { + return RubyString.newString(context.runtime, auth_tag); + } + return RubyString.newString(context.runtime, (ByteList) auth_tag.subSequence(0, tag_len)); } if ( ! isAuthDataMode() ) { throw newCipherError(context.runtime, "authentication tag not supported by this cipher"); @@ -1296,14 +1299,18 @@ public IRubyObject set_auth_tag(final ThreadContext context, final IRubyObject t return auth_tag; } + @JRubyMethod(name = "auth_tag_len=") + public IRubyObject set_auth_tag_len(IRubyObject tag_len) { + this.auth_tag_len = tag_len.convertToInteger().getIntValue(); + return tag_len; + } + private boolean isAuthDataMode() { // Authenticated Encryption with Associated Data (AEAD) return "GCM".equalsIgnoreCase(cryptoMode) || "CCM".equalsIgnoreCase(cryptoMode); } - private static final int MAX_AUTH_TAG_LENGTH = 16; - private int getAuthTagLength() { - return Math.min(MAX_AUTH_TAG_LENGTH, this.key.length); // in bytes + return Math.min(auth_tag_len, this.key.length); // in bytes } private transient ByteList auth_data; @@ -1355,22 +1362,10 @@ public IRubyObject random_iv(final ThreadContext context) { this.set_iv(context, str); return str; } - //String getAlgorithm() { - // return this.cipher.getAlgorithm(); - //} - final String getName() { return this.name; } - //String getCryptoBase() { - // return this.cryptoBase; - //} - - //String getCryptoMode() { - // return this.cryptoMode; - //} - final int getKeyLength() { return keyLength; } diff --git a/src/main/java/org/jruby/ext/openssl/CipherStrings.java b/src/main/java/org/jruby/ext/openssl/CipherStrings.java index c626fd9b..160d61b6 100644 --- a/src/main/java/org/jruby/ext/openssl/CipherStrings.java +++ b/src/main/java/org/jruby/ext/openssl/CipherStrings.java @@ -38,6 +38,11 @@ import java.util.Map; import java.util.Set; +import static org.jruby.ext.openssl.SSL.SSL3_VERSION; +import static org.jruby.ext.openssl.SSL.TLS1_VERSION; +import static org.jruby.ext.openssl.SSL.TLS1_1_VERSION; +import static org.jruby.ext.openssl.SSL.TLS1_2_VERSION; + /** * * @author Ola Bini @@ -149,49 +154,108 @@ public class CipherStrings { public final static String SSL_TXT_kRSA = "kRSA"; public final static String SSL_TXT_kDHr = "kDHr"; public final static String SSL_TXT_kDHd = "kDHd"; - public final static String SSL_TXT_kEDH = "kEDH"; + public final static String SSL_TXT_kEDH = "kEDH"; /* alias for kDHE */ + public final static String SSL_TXT_kDHE = "kDHE"; + public final static String SSL_TXT_kEECDH = "kEECDH"; /* alias for kECDHE */ + public final static String SSL_TXT_kECDHE = "kECDHE"; + public final static String SSL_TXT_aRSA = "aRSA"; public final static String SSL_TXT_aDSS = "aDSS"; public final static String SSL_TXT_aDH = "aDH"; + public final static String SSL_TXT_aECDSA = "aECDSA"; + public final static String SSL_TXT_DSS = "DSS"; public final static String SSL_TXT_DH = "DH"; - public final static String SSL_TXT_EDH = "EDH"; + public final static String SSL_TXT_DHE = "DHE"; /* same as "kDHE:-ADH" */; + public final static String SSL_TXT_EDH = "EDH"; /* alias for DHE */ public final static String SSL_TXT_ADH = "ADH"; public final static String SSL_TXT_RSA = "RSA"; + public final static String SSL_TXT_ECDH = "ECDH"; + public final static String SSL_TXT_EECDH = "EECDH"; /* alias for ECDHE" */ + public final static String SSL_TXT_ECDHE = "ECDHE"; /* same as "kECDHE:-AECDH" */ + public final static String SSL_TXT_AECDH = "AECDH"; + public final static String SSL_TXT_ECDSA = "ECDSA"; + //public final static String SSL_TXT_PSK = "PSK"; + //public final static String SSL_TXT_SRP = "SRP"; + public final static String SSL_TXT_DES = "DES"; public final static String SSL_TXT_3DES = "3DES"; public final static String SSL_TXT_RC4 = "RC4"; public final static String SSL_TXT_RC2 = "RC2"; public final static String SSL_TXT_IDEA = "IDEA"; + public final static String SSL_TXT_SEED = "SEED"; + public final static String SSL_TXT_AES128 = "AES128"; + public final static String SSL_TXT_AES256 = "AES256"; public final static String SSL_TXT_AES = "AES"; + public final static String SSL_TXT_AES_GCM = "AESGCM"; + public final static String SSL_TXT_AES_CCM = "AESCCM"; + public final static String SSL_TXT_AES_CCM_8 = "AESCCM8"; + public final static String SSL_TXT_CAMELLIA128 = "CAMELLIA128"; + public final static String SSL_TXT_CAMELLIA256 = "CAMELLIA256"; + public final static String SSL_TXT_CAMELLIA = "CAMELLIA"; + public final static String SSL_TXT_CHACHA20 = "CHACHA20"; + public final static String SSL_TXT_GOST = "GOST89"; + public final static String SSL_TXT_ARIA = "ARIA"; + public final static String SSL_TXT_ARIA_GCM = "ARIAGCM"; + public final static String SSL_TXT_ARIA128 = "ARIA128"; + public final static String SSL_TXT_ARIA256 = "ARIA256"; + public final static String SSL_TXT_MD5 = "MD5"; public final static String SSL_TXT_SHA1 = "SHA1"; - public final static String SSL_TXT_SHA = "SHA"; + public final static String SSL_TXT_SHA = "SHA";/* same as "SHA1" */ + public final static String SSL_TXT_SHA256 = "SHA256"; + public final static String SSL_TXT_SHA384 = "SHA384"; + public final static String SSL_TXT_EXP = "EXP"; public final static String SSL_TXT_EXPORT = "EXPORT"; public final static String SSL_TXT_EXP40 = "EXPORT40"; public final static String SSL_TXT_EXP56 = "EXPORT56"; + public final static String SSL_TXT_SSLV2 = "SSLv2"; public final static String SSL_TXT_SSLV3 = "SSLv3"; public final static String SSL_TXT_TLSV1 = "TLSv1"; + public final static String SSL_TXT_TLSV1_1 = "TLSv1.1"; + public final static String SSL_TXT_TLSV1_2 = "TLSv1.2"; + public final static String SSL_TXT_ALL = "ALL"; + public final static String SSL_TXT_ECC = "ECCdraft"; public final static String SSL_TXT_CMPALL = "COMPLEMENTOFALL"; public final static String SSL_TXT_CMPDEF = "COMPLEMENTOFDEFAULT"; - // "ALL:!aNULL:!eNULL:!SSLv2" is for OpenSSL 1.0.0 GA - public final static String SSL_DEFAULT_CIPHER_LIST = "AES:ALL:!aNULL:!eNULL:+RC4:@STRENGTH"; + // NOTE: Default list of TLSv1.2 (and earlier) ciphers deprecated in 3.0.0 + // "ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2" in OpenSSL 1.0.2 + public final static String SSL_DEFAULT_CIPHER_LIST; // = "AES:ALL:!aNULL:!eNULL:!SSLv2:+RC4:@STRENGTH"; + /* + * The following cipher list is used by default. It also is substituted when + * an application-defined cipher list string starts with 'DEFAULT'. + * This applies to ciphersuites for TLSv1.2 and below. + */ + //public final static String SSL_DEFAULT_CIPHER_LIST = "ALL:!COMPLEMENTOFDEFAULT:!eNULL"; // OpenSSL 1.1.1 + /* This is the default set of TLSv1.3 ciphersuites */ + public final static String TLS_DEFAULT_CIPHERSUITES = "TLS_AES_256_GCM_SHA384:" + + "TLS_CHACHA20_POLY1305_SHA256:" + + "TLS_AES_128_GCM_SHA256"; + static { // TODO REVIEW 1.3 cipher matching + SSL_DEFAULT_CIPHER_LIST = "ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2"; // + ':' + TLS_DEFAULT_CIPHERSUITES; + } public final static long SSL_MKEY_MASK = 0x000000FFL; + /* Bits for algorithm_mkey (key exchange algorithm) */ + /* RSA key exchange */ public final static long SSL_kRSA = 0x00000001L; public final static long SSL_kDHr = 0x00000002L; public final static long SSL_kDHd = 0x00000004L; public final static long SSL_kFZA = 0x00000008L; - public final static long SSL_kEDH = 0x00000010L; + /* tmp DH key no DH cert */ + public final static long SSL_kDHE = 0x00000010L; + public final static long SSL_kEDH = SSL_kDHE; /* synonym */ public final static long SSL_kKRB5 = 0x00000020L; public final static long SSL_kECDH = 0x00000040L; + /* ephemeral ECDH */ public final static long SSL_kECDHE = 0x00000080L; + public final static long SSL_kEECDH = SSL_kECDHE; /* synonym */ public final static long SSL_aNULL = 0x00000800L; public final static long SSL_AUTH_MASK = 0x00007F00L; public final static long SSL_EDH = (SSL_kEDH|(SSL_AUTH_MASK^SSL_aNULL)); @@ -202,9 +266,7 @@ public class CipherStrings { public final static long SSL_aDH = 0x00001000L; public final static long SSL_aKRB5 = 0x00002000L; public final static long SSL_aECDSA = 0x00004000L; - public final static long SSL_eNULL = 0x00200000L; public final static long SSL_eFZA = 0x00100000L; - public final static long SSL_NULL = (SSL_eNULL); public final static long SSL_ADH = (SSL_kEDH|SSL_aNULL); public final static long SSL_RSA = (SSL_kRSA|SSL_aRSA); public final static long SSL_DH = (SSL_kDHr|SSL_kDHd|SSL_kEDH); @@ -212,17 +274,55 @@ public class CipherStrings { public final static long SSL_FZA = (SSL_aFZA|SSL_kFZA|SSL_eFZA); public final static long SSL_KRB5 = (SSL_kKRB5|SSL_aKRB5); public final static long SSL_ENC_MASK = 0x043F8000L; - public final static long SSL_DES = 0x00008000L; - public final static long SSL_3DES = 0x00010000L; - public final static long SSL_RC4 = 0x00020000L; - public final static long SSL_RC2 = 0x00040000L; - public final static long SSL_IDEA = 0x00080000L; - public final static long SSL_AES = 0x04000000L; + + /* Bits for algorithm_enc (symmetric encryption) */ + public final static long SSL_DES = 0x00000001L; + public final static long SSL_3DES = 0x00000002L; + public final static long SSL_RC4 = 0x00000004L; + public final static long SSL_RC2 = 0x00000008L; + public final static long SSL_IDEA = 0x00000010L; + public final static long SSL_eNULL = 0x00000020L; + //public final static long SSL_AES = 0x04000000L; + public final static long SSL_AES128 = 0x00000040L; + public final static long SSL_AES256 = 0x00000080L; + public final static long SSL_CAMELLIA128 = 0x00000100L; + public final static long SSL_CAMELLIA256 = 0x00000200L; + public final static long SSL_eGOST2814789CNT = 0x00000400L; + public final static long SSL_SEED = 0x00000800L; + public final static long SSL_AES128GCM = 0x00001000L; + public final static long SSL_AES256GCM = 0x00002000L; + public final static long SSL_AES128CCM = 0x00004000L; + public final static long SSL_AES256CCM = 0x00008000L; + public final static long SSL_AES128CCM8 = 0x00010000L; + public final static long SSL_AES256CCM8 = 0x00020000L; + public final static long SSL_eGOST2814789CNT12 = 0x00040000L; + public final static long SSL_CHACHA20POLY1305 = 0x00080000L; + public final static long SSL_ARIA128GCM = 0x00100000L; + public final static long SSL_ARIA256GCM = 0x00200000L; + + public final static long SSL_AESGCM = (SSL_AES128GCM | SSL_AES256GCM); + public final static long SSL_AESCCM = (SSL_AES128CCM | SSL_AES256CCM | SSL_AES128CCM8 | SSL_AES256CCM8); + public final static long SSL_AES = (SSL_AES128|SSL_AES256|SSL_AESGCM|SSL_AESCCM); + public final static long SSL_CAMELLIA = (SSL_CAMELLIA128|SSL_CAMELLIA256); + public final static long SSL_CHACHA20 = (SSL_CHACHA20POLY1305); + public final static long SSL_ARIAGCM = (SSL_ARIA128GCM | SSL_ARIA256GCM); + public final static long SSL_ARIA = (SSL_ARIAGCM); + + /* Bits for algorithm_mac (symmetric authentication) */ + public final static long SSL_MAC_MASK = 0x00c00000L; - public final static long SSL_MD5 = 0x00400000L; - public final static long SSL_SHA1 = 0x00800000L; + /* Bits for algorithm_mac (symmetric authentication) */ + public final static long SSL_MD5 = 0x00400000L; // 0x00000001U + public final static long SSL_SHA1 = 0x00800000L; // 0x00000002U public final static long SSL_SHA = (SSL_SHA1); - public final static long SSL_SSL_MASK = 0x03000000L; + //# define SSL_GOST94 0x00000004U + //# define SSL_GOST89MAC 0x00000008U + // NOTE: retrofitted (from 1.1.1) - expected not to used | and w SSL_MAC_MASK + public final static long SSL_SHA256 = 0x00000010L; + public final static long SSL_SHA384 = 0x00000020L; + + @Deprecated + public final static long SSL_SSL_MASK = 0x03000000L; // legacy public final static long SSL_SSLV2 = 0x01000000L; public final static long SSL_SSLV3 = 0x02000000L; public final static long SSL_TLSV1 = SSL_SSLV3; @@ -235,11 +335,19 @@ public class CipherStrings { public final static long SSL_MICRO = (SSL_EXP40); public final static long SSL_EXP56 = 0x00000010L; public final static long SSL_MINI = (SSL_EXP56); - public final static long SSL_LOW = 0x00000020L; - public final static long SSL_MEDIUM = 0x00000040L; - public final static long SSL_HIGH = 0x00000080L; - public final static long SSL_ALL = 0xffffffffL; + + // NOTE: can not be adjusted until SSL_NOT_EXP is around! + public final static long SSL_LOW = 0x00000020L; // 0x00000002U in OSSL 1.1 + public final static long SSL_MEDIUM = 0x00000040L; // 0x00000004U in OSSL 1.1 + public final static long SSL_HIGH = 0x00000080L; // 0x00000008U in OSSL 1.1 + public final static long SSL_FIPS = 0x00000100L; // 0x00000010U in OSSL 1.1 + public final static long SSL_NOT_DEFAULT = 0x00000200L; // 0x00000020U in OSSL 1.1 + + @Deprecated + public final static long SSL_ALL = 0xffffffffL; // legacy + @Deprecated public final static long SSL_ALL_CIPHERS = (SSL_MKEY_MASK|SSL_AUTH_MASK|SSL_ENC_MASK|SSL_MAC_MASK); + @Deprecated public final static long SSL_ALL_STRENGTHS = (SSL_EXP_MASK|SSL_STRONG_MASK); public final static long SSL_PKEY_RSA_ENC = 0; public final static long SSL_PKEY_RSA_SIGN = 1; @@ -371,25 +479,36 @@ public class CipherStrings { public final static String TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA = "ECDHE-RSA-DES-CBC3-SHA"; public final static String TLS1_TXT_ECDH_anon_WITH_NULL_SHA = "AECDH-NULL-SHA"; + // struct ssl_cipher_st static final class Def implements Comparable, Cloneable { - //private final byte valid; + final boolean valid; // TODO NOT IMPLEMENTED! final String name; - //private final long id; + private final long id; + final long algorithms; private final long algStrength; //final long algorithm2; - final int algStrengthBits; - final int algBits; + final int algStrengthBits; // bits + final int algBits; // alg_bits private final long mask; private final long algStrengthMask; + // OpenSSL 1.1.1 + private long algorithm_mkey; + private long algorithm_auth; + private long algorithm_enc; + private long algorithm_mac; + private int min_tls; // "new" format using SSL.TLS_ constants + private int max_tls; // "new" format using SSL.TLS_ constants + + // JOSSL extra private volatile String cipherSuite; Def(int valid, String name, long id, long algorithms, long algo_strength, long algorithm2, int strength_bits, int alg_bits, long mask, long maskStrength) { - //this.valid = (byte) valid; + this.valid = valid != 0; this.name = name; - //this.id = id; + this.id = id; this.algorithms = algorithms; this.algStrength = algo_strength; //this.algorithm2 = algorithm2; @@ -400,7 +519,9 @@ static final class Def implements Comparable, Cloneable { } Def(String name, long algorithms, long algo_strength, int strength_bits, int alg_bits, long mask, long maskStrength) { + this.valid = true; this.name = name; + this.id = 0; this.algorithms = algorithms; this.algStrength = algo_strength; this.algStrengthBits = strength_bits; @@ -409,6 +530,40 @@ static final class Def implements Comparable, Cloneable { this.algStrengthMask = maskStrength; } + Def(int valid, String name, String stdname, /* RFC name */ + long id, /* uint32_t id, 4 bytes, first is version */ + /* + * changed in 1.0.0: these four used to be portions of a single value + * 'algorithms' + */ + long algorithm_mkey, /* key exchange algorithm */ + long algorithm_auth, /* server authentication */ + long algorithm_enc, /* symmetric encryption */ + long algorithm_mac, /* symmetric authentication */ + int min_tls, /* minimum SSL/TLS protocol version */ + int max_tls /* maximum SSL/TLS protocol version */) { + + this.valid = valid != 0; + this.name = name; + this.id = id; + + this.algorithm_mkey = algorithm_mkey; + this.algorithm_auth = algorithm_auth; + this.algorithm_enc = algorithm_enc; + this.algorithm_mac = algorithm_mac; + this.min_tls = min_tls; + this.max_tls = max_tls; + + this.algorithms = algorithm_mkey; + this.algStrength = 0; + this.algStrengthBits = 0; + this.algBits = 0; + + this.mask = 0; + this.algStrengthMask = 0; + } + + public String getCipherSuite() { return cipherSuite; } @@ -512,8 +667,10 @@ static Collection matchingCiphers(final String cipherString, final String[] } int index = 0; - switch ( part.charAt(0) ) { - case '!': case '+': case '-': index++; break; + if (part.length() > 0) { + switch ( part.charAt(0) ) { + case '!': case '+': case '-': index++; break; + } } final Collection matching; @@ -561,9 +718,22 @@ static Collection matchingCiphers(final String cipherString, final String[] private static Collection matchingExact(final String name, final String[] all, final boolean setSuite) { final Def pattern = Definitions.get(name); - if ( pattern != null ) { + if (pattern != null) { return matchingPattern(pattern, all, true, setSuite); } + + final Def def = CipherNames.get(name); + if (def != null) { + if (setSuite) { + for (final String entry : all) { + if (name.equals(SuiteToOSSL.get(entry))) { + return Collections.singleton(def.setCipherSuite(entry)); + } + } + } else { + return Collections.singleton(def); + } + } return null; // Collections.emptyList(); } @@ -611,7 +781,7 @@ private static Collection matchingPattern( private final static Map Definitions; //private final static ArrayList Ciphers; private final static Map CipherNames; - private final static Map SuiteToOSSL; + final static Map SuiteToOSSL; static { Definitions = new HashMap( 48, 1 ); @@ -646,7 +816,7 @@ private static Collection matchingPattern( Definitions.put(SSL_TXT_MD5,new Def(0,SSL_TXT_MD5, 0,SSL_MD5, 0,0,0,0,SSL_MAC_MASK,0)); Definitions.put(SSL_TXT_SHA1,new Def(0,SSL_TXT_SHA1,0,SSL_SHA1, 0,0,0,0,SSL_MAC_MASK,0)); Definitions.put(SSL_TXT_SHA,new Def(0,SSL_TXT_SHA, 0,SSL_SHA, 0,0,0,0,SSL_MAC_MASK,0)); - Definitions.put(SSL_TXT_NULL,new Def(0,SSL_TXT_NULL,0,SSL_NULL, 0,0,0,0,SSL_ENC_MASK,0)); + Definitions.put(SSL_TXT_NULL,new Def(0,SSL_TXT_NULL,0,SSL_eNULL, 0,0,0,0,SSL_ENC_MASK,0)); Definitions.put(SSL_TXT_KRB5,new Def(0,SSL_TXT_KRB5,0,SSL_KRB5, 0,0,0,0,SSL_AUTH_MASK|SSL_MKEY_MASK,0)); Definitions.put(SSL_TXT_RSA,new Def(0,SSL_TXT_RSA, 0,SSL_RSA, 0,0,0,0,SSL_AUTH_MASK|SSL_MKEY_MASK,0)); Definitions.put(SSL_TXT_ADH,new Def(0,SSL_TXT_ADH, 0,SSL_ADH, 0,0,0,0,SSL_AUTH_MASK|SSL_MKEY_MASK,0)); @@ -1999,10 +2169,13 @@ private static Collection matchingPattern( )); SuiteToOSSL.put("TLS_ECDHE_ECDSA_WITH_NULL_SHA", "ECDHE-ECDSA-NULL-SHA"); - SuiteToOSSL.put("TLS_ECDHE_RSA_WITH_NULL_SHA", "ECDHE-RSA-NULL-SHA"); - SuiteToOSSL.put("TLS_ECDH_ECDSA_WITH_NULL_SHA", "ECDH-ECDSA-NULL-SHA"); - SuiteToOSSL.put("TLS_ECDH_RSA_WITH_NULL_SHA", "ECDH-RSA-NULL-SHA"); - SuiteToOSSL.put("TLS_ECDH_anon_WITH_NULL_SHA", "AECDH-NULL-SHA"); + SuiteToOSSL.put("TLS_ECDHE_RSA_WITH_NULL_SHA", "ECDHE-RSA-NULL-SHA"); + SuiteToOSSL.put("TLS_ECDH_ECDSA_WITH_NULL_SHA", "ECDH-ECDSA-NULL-SHA"); + SuiteToOSSL.put("TLS_ECDH_RSA_WITH_NULL_SHA", "ECDH-RSA-NULL-SHA"); + SuiteToOSSL.put("TLS_ECDH_anon_WITH_NULL_SHA", "AECDH-NULL-SHA"); + + SuiteToOSSL.put("TLS_DH_anon_WITH_AES_128_GCM_SHA256", "ADH-AES128-GCM-SHA256"); + SuiteToOSSL.put("TLS_DH_anon_WITH_AES_256_GCM_SHA384", "ADH-AES256-GCM-SHA384"); /* For IBM JRE: suite names start with "SSL_". On Oracle JRE, the suite names start with "TLS_" */ SuiteToOSSL.put("SSL_DH_anon_WITH_AES_128_CBC_SHA", "ADH-AES128-SHA"); @@ -2070,19 +2243,38 @@ private static Collection matchingPattern( SuiteToOSSL.put("SSL_RSA_WITH_AES_256_GCM_SHA384", "AES256-GCM-SHA384"); SuiteToOSSL.put("SSL_RSA_WITH_NULL_SHA256", "NULL-SHA256"); - // left overs supported by Java 7's SSLv3 / TLS v1.2 : - - // TLS_EMPTY_RENEGOTIATION_INFO_SCSV, - // TLS_KRB5_WITH_3DES_EDE_CBC_SHA, - // TLS_KRB5_WITH_3DES_EDE_CBC_MD5, - // TLS_KRB5_WITH_RC4_128_SHA, - // TLS_KRB5_WITH_RC4_128_MD5, - // TLS_KRB5_WITH_DES_CBC_SHA, - // TLS_KRB5_WITH_DES_CBC_MD5, - // TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, - // TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5, - // TLS_KRB5_EXPORT_WITH_RC4_40_SHA, - // TLS_KRB5_EXPORT_WITH_RC4_40_MD5 + // TLS v1.3 (Java 8/11) streaming ciphers : + // TODO the specifics of using these on 1.3 only is not implemented + + SuiteToOSSL.put("TLS_AES_128_GCM_SHA256", name = "TLS_AES_128_GCM_SHA256"); + CipherNames.put(name, new Def(name, + SSL_AES|SSL_SHA|SSL_TLSV1, + SSL_NOT_EXP, 128, 256, SSL_ALL_CIPHERS, SSL_ALL_STRENGTHS + )); + + SuiteToOSSL.put("TLS_AES_256_GCM_SHA384", name = "TLS_AES_256_GCM_SHA384"); + CipherNames.put(name, new Def(name, + SSL_AES|SSL_SHA|SSL_TLSV1, + SSL_NOT_EXP, 256, 384, SSL_ALL_CIPHERS, SSL_ALL_STRENGTHS + )); + + SuiteToOSSL.put("TLS_CHACHA20_POLY1305_SHA256", name = "TLS_CHACHA20_POLY1305_SHA256"); + CipherNames.put(name, new Def(name, + SSL_CHACHA20|SSL_SHA|SSL_TLSV1, + SSL_NOT_EXP, 256, 256, SSL_ALL_CIPHERS, SSL_ALL_STRENGTHS + )); + + SuiteToOSSL.put("TLS_AES_128_CCM_SHA256", name = "TLS_AES_128_CCM_SHA256"); + CipherNames.put(name, new Def(name, + SSL_AES|SSL_SHA|SSL_TLSV1, + SSL_NOT_EXP, 128, 256, SSL_ALL_CIPHERS, SSL_ALL_STRENGTHS + )); + + SuiteToOSSL.put("TLS_AES_128_CCM_8_SHA256", name = "TLS_AES_128_CCM_8_SHA256"); + CipherNames.put(name, new Def(name, + SSL_AES|SSL_SHA|SSL_TLSV1, + SSL_NOT_EXP, 256, 384, SSL_ALL_CIPHERS, SSL_ALL_STRENGTHS + )); for ( Def def : Ciphers ) CipherNames.put(def.name, def); diff --git a/src/main/java/org/jruby/ext/openssl/Config.java b/src/main/java/org/jruby/ext/openssl/Config.java deleted file mode 100644 index 58d7f663..00000000 --- a/src/main/java/org/jruby/ext/openssl/Config.java +++ /dev/null @@ -1,52 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - * Version: EPL 1.0/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Eclipse Public - * License Version 1.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.eclipse.org/legal/epl-v10.html - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * Copyright (C) 2006 Ola Bini - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the EPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the EPL, the GPL or the LGPL. - ***** END LICENSE BLOCK *****/ -package org.jruby.ext.openssl; - -import org.jruby.Ruby; -import org.jruby.RubyClass; -import org.jruby.RubyModule; - -/** - * OpenSSL::Config (native parts) - * @author Ola Bini - */ -@Deprecated // move to .rb (for now) as its now lazy loaded ... -public class Config { - // TODO: we cannot detect OS's default config file. ignore? - // public static final String DEFAULT_CONFIG_FILE = "./openssl.cnf"; - - public static void createConfig(Ruby runtime, RubyModule OpenSSL) { - RubyClass Config = OpenSSL.defineClassUnder("Config", runtime.getObject(), runtime.getObject().getAllocator()); - Config.defineAnnotatedMethods(Config.class); - RubyClass openSSLError = OpenSSL.getClass("OpenSSLError"); - OpenSSL.defineClassUnder("ConfigError", openSSLError, openSSLError.getAllocator()); - // TODO: we should define this constant with proper path. (see above) - Config.setConstant("DEFAULT_CONFIG_FILE", runtime.getNil()); // runtime.newString(DEFAULT_CONFIG_FILE) - } - -} diff --git a/src/main/java/org/jruby/ext/openssl/Digest.java b/src/main/java/org/jruby/ext/openssl/Digest.java index c73f2860..76f50377 100644 --- a/src/main/java/org/jruby/ext/openssl/Digest.java +++ b/src/main/java/org/jruby/ext/openssl/Digest.java @@ -52,16 +52,12 @@ public class Digest extends RubyObject { private static final long serialVersionUID = 7409857414064319518L; - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public Digest allocate(Ruby runtime, RubyClass klass) { return new Digest(runtime, klass); } - }; - static void createDigest(Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { runtime.getLoadService().require("digest"); final RubyModule coreDigest = runtime.getModule("Digest"); final RubyClass DigestClass = coreDigest.getClass("Class"); // ::Digest::Class - RubyClass Digest = OpenSSL.defineClassUnder("Digest", DigestClass, ALLOCATOR); + RubyClass Digest = OpenSSL.defineClassUnder("Digest", DigestClass, (r, klass) -> new Digest(r, klass)); OpenSSL.defineClassUnder("DigestError", OpenSSLError, OpenSSLError.getAllocator()); Digest.defineAnnotatedMethods(Digest.class); diff --git a/src/main/java/org/jruby/ext/openssl/HMAC.java b/src/main/java/org/jruby/ext/openssl/HMAC.java index d4487148..03bc4e92 100644 --- a/src/main/java/org/jruby/ext/openssl/HMAC.java +++ b/src/main/java/org/jruby/ext/openssl/HMAC.java @@ -37,7 +37,6 @@ import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.ByteList; import org.jruby.runtime.Visibility; @@ -50,12 +49,8 @@ public class HMAC extends RubyObject { private static final long serialVersionUID = 7602535792884680307L; - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public HMAC allocate(Ruby runtime, RubyClass klass) { return new HMAC(runtime, klass); } - }; - static void createHMAC(final Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { - RubyClass HMAC = OpenSSL.defineClassUnder("HMAC", runtime.getObject(), ALLOCATOR); + RubyClass HMAC = OpenSSL.defineClassUnder("HMAC", runtime.getObject(), (r, klass) -> new HMAC(r, klass)); OpenSSL.defineClassUnder("HMACError", OpenSSLError, OpenSSLError.getAllocator()); HMAC.defineAnnotatedMethods(HMAC.class); } diff --git a/src/main/java/org/jruby/ext/openssl/NetscapeSPKI.java b/src/main/java/org/jruby/ext/openssl/NetscapeSPKI.java index 30dc3eb1..0972207a 100644 --- a/src/main/java/org/jruby/ext/openssl/NetscapeSPKI.java +++ b/src/main/java/org/jruby/ext/openssl/NetscapeSPKI.java @@ -45,11 +45,9 @@ import org.jruby.RubyClass; import org.jruby.RubyModule; import org.jruby.RubyObject; -import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.ext.openssl.impl.Base64; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; @@ -58,8 +56,6 @@ // org.bouncycastle.jce.netscape.NetscapeCertRequest emulator: import org.jruby.ext.openssl.impl.NetscapeCertRequest; -import static org.jruby.ext.openssl.PKeyDSA._DSA; -import static org.jruby.ext.openssl.PKeyRSA._RSA; import static org.jruby.ext.openssl.OpenSSL.*; /** @@ -68,15 +64,9 @@ public class NetscapeSPKI extends RubyObject { private static final long serialVersionUID = 3211242351810109432L; - private static ObjectAllocator NETSCAPESPKI_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new NetscapeSPKI(runtime, klass); - } - }; - static void createNetscapeSPKI(Ruby runtime, final RubyModule OpenSSL, final RubyClass OpenSSLError) { RubyModule Netscape = OpenSSL.defineModuleUnder("Netscape"); - RubyClass SPKI = Netscape.defineClassUnder("SPKI",runtime.getObject(),NETSCAPESPKI_ALLOCATOR); + RubyClass SPKI = Netscape.defineClassUnder("SPKI", runtime.getObject(), (r, klass) -> new NetscapeSPKI(r, klass)); Netscape.defineClassUnder("SPKIError", OpenSSLError, OpenSSLError.getAllocator()); SPKI.defineAnnotatedMethods(NetscapeSPKI.class); } @@ -109,19 +99,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a catch (GeneralSecurityException e) { throw newSPKIError(e); } catch (IllegalArgumentException e) { throw newSPKIError(e); } - final PublicKey publicKey = cert.getPublicKey(); - final String algorithm = publicKey.getAlgorithm(); - final RubyString pub_key = RubyString.newString(runtime, publicKey.getEncoded()); - - if ( "RSA".equalsIgnoreCase(algorithm) ) { - this.public_key = _RSA(runtime).callMethod(context, "new", pub_key); - } - else if ( "DSA".equalsIgnoreCase(algorithm) ) { - this.public_key = _DSA(runtime).callMethod(context, "new", pub_key); - } - else { - throw runtime.newLoadError("not implemented algo for public key: " + algorithm); - } + this.public_key = PKey.newInstance(runtime, cert.getPublicKey()); } return this; } diff --git a/src/main/java/org/jruby/ext/openssl/OCSPBasicResponse.java b/src/main/java/org/jruby/ext/openssl/OCSPBasicResponse.java index 8e07b52e..b6912b1e 100644 --- a/src/main/java/org/jruby/ext/openssl/OCSPBasicResponse.java +++ b/src/main/java/org/jruby/ext/openssl/OCSPBasicResponse.java @@ -88,7 +88,6 @@ import org.jruby.ext.openssl.x509store.X509AuxCertificate; import org.jruby.ext.openssl.x509store.X509Utils; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -117,14 +116,8 @@ public class OCSPBasicResponse extends RubyObject { private static final String OCSP_RESPID_KEY = "RESPID_KEY"; private static final String OCSP_TRUSTOTHER = "TRUSTOTHER"; - private static ObjectAllocator BASICRESPONSE_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new OCSPBasicResponse(runtime, klass); - } - }; - public static void createBasicResponse(final Ruby runtime, final RubyModule OCSP) { - RubyClass BasicResponse = OCSP.defineClassUnder("BasicResponse", runtime.getObject(), BASICRESPONSE_ALLOCATOR); + RubyClass BasicResponse = OCSP.defineClassUnder("BasicResponse", runtime.getObject(), (r, klass) -> new OCSPBasicResponse(r, klass)); BasicResponse.defineAnnotatedMethods(OCSPBasicResponse.class); } @@ -357,8 +350,12 @@ public IRubyObject sign(final ThreadContext context, IRubyObject[] args) { } try { - Extension[] respExtAry = new Extension[extensions.size()]; - Extensions respExtensions = new Extensions(extensions.toArray(respExtAry)); + final Extensions respExtensions; + if (extensions != null && extensions.size() > 0) { + respExtensions = new Extensions(extensions.toArray(new Extension[extensions.size()])); + } else { + respExtensions = null; + } BasicOCSPResp bcBasicOCSPResp = respBuilder.setResponseExtensions(respExtensions).build(contentSigner, chain, producedAt); asn1BCBasicOCSPResp = BasicOCSPResponse.getInstance(bcBasicOCSPResp.getEncoded()); } @@ -530,6 +527,9 @@ private boolean checkDelegated(X509Cert signerCA) { catch (CertificateParsingException e) { throw newOCSPError(getRuntime(), e); } + catch (IOException e) { + throw newOCSPError(getRuntime(), e); + } } private boolean matchIssuerId(X509Cert signerCA, CertificateID certId, List singleResponses) throws IOException { @@ -590,22 +590,21 @@ else if (intOrTime instanceof RubyTime) { return new ASN1GeneralizedTime(retTime); } - private Extensions convertRubyExtensions(IRubyObject extensions) { - if (extensions.isNil()) return null; - List retExtensions = new ArrayList(); - Iterator rubyExtensions = ((RubyArray)extensions).iterator(); - while (rubyExtensions.hasNext()) { - X509Extension rubyExt = (X509Extension)rubyExtensions.next(); - Extension ext = Extension.getInstance(((RubyString)rubyExt.to_der()).getBytes()); - retExtensions.add(ext); - } - Extension[] exts = new Extension[retExtensions.size()]; - retExtensions.toArray(exts); - return new Extensions(exts); + private Extensions convertRubyExtensions(final IRubyObject arg) { + if (arg.isNil()) return null; + final RubyArray rubyExts = arg.convertToArray(); // Array + if (rubyExts.isEmpty()) return null; + + final Extension[] extensions = new Extension[rubyExts.size()]; + for (int i = 0; i convertRubyCerts(IRubyObject certificates) { - Iterator it = ((RubyArray)certificates).iterator(); + Iterator it = certificates.convertToArray().iterator(); List ret = new ArrayList(); while (it.hasNext()) { ret.add(it.next()); diff --git a/src/main/java/org/jruby/ext/openssl/OCSPCertificateId.java b/src/main/java/org/jruby/ext/openssl/OCSPCertificateId.java index 67066b0b..ec266009 100644 --- a/src/main/java/org/jruby/ext/openssl/OCSPCertificateId.java +++ b/src/main/java/org/jruby/ext/openssl/OCSPCertificateId.java @@ -52,7 +52,6 @@ import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -69,14 +68,8 @@ public class OCSPCertificateId extends RubyObject { private static final long serialVersionUID = 6324454052172773918L; - private static ObjectAllocator CERTIFICATEID_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new OCSPCertificateId(runtime, klass); - } - }; - public static void createCertificateId(final Ruby runtime, final RubyModule _OCSP) { - RubyClass _certificateId = _OCSP.defineClassUnder("CertificateId", runtime.getObject(), CERTIFICATEID_ALLOCATOR); + RubyClass _certificateId = _OCSP.defineClassUnder("CertificateId", runtime.getObject(), (r, klass) -> new OCSPCertificateId(r, klass)); _certificateId.defineAnnotatedMethods(OCSPCertificateId.class); } @@ -101,39 +94,35 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject subject, originalIssuer = (X509Cert) issuer; BigInteger serial = subjectCert.getSerial(); - return initializeImpl(context, serial, originalIssuer, digest); + return initializeImpl(context.runtime, serial, originalIssuer, digest); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject subject, IRubyObject issuer) { - Ruby runtime = context.getRuntime(); + final Ruby runtime = context.runtime; X509Cert subjectCert = (X509Cert) subject; originalIssuer = (X509Cert) issuer; BigInteger serial = subjectCert.getSerial(); - Digest digestInstance = new Digest(runtime, _Digest(runtime)); - IRubyObject digest = digestInstance.initialize(context, new IRubyObject[] { RubyString.newString(runtime, "SHA1") }); + Digest digest = new Digest(runtime, _Digest(runtime)); + digest.initializeImpl(runtime, RubyString.newString(runtime, "SHA1"), runtime.getNil()); - return initializeImpl(context, serial, originalIssuer, digest); + return initializeImpl(runtime, serial, originalIssuer, digest); } @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE) public IRubyObject initialize(final ThreadContext context, IRubyObject der) { - Ruby runtime = context.getRuntime(); - RubyString derStr = StringHelper.readPossibleDERInput(context, der); - try { + try { return initializeImpl(derStr.getBytes()); } - catch (IOException e) { - throw newOCSPError(runtime, e); + catch (Exception e) { + throw newOCSPError(context.runtime, e); } } - private IRubyObject initializeImpl(final ThreadContext context, BigInteger serial, - IRubyObject issuerCert, IRubyObject digest) { - Ruby runtime = context.getRuntime(); + private IRubyObject initializeImpl(final Ruby runtime, BigInteger serial, X509Cert issuerCert, IRubyObject digest) { Digest rubyDigest = (Digest) digest; ASN1ObjectIdentifier oid = ASN1.sym2Oid(runtime, rubyDigest.getName().toLowerCase()); @@ -147,10 +136,8 @@ private IRubyObject initializeImpl(final ThreadContext context, BigInteger seria throw newOCSPError(runtime, e); } - X509Cert rubyCert = (X509Cert) issuerCert; - try { - this.bcCertId = new CertificateID(calc, new X509CertificateHolder(rubyCert.getAuxCert().getEncoded()), serial).toASN1Primitive(); + this.bcCertId = new CertificateID(calc, new X509CertificateHolder(issuerCert.getAuxCert().getEncoded()), serial).toASN1Primitive(); } catch (Exception e) { throw newOCSPError(runtime, e); @@ -159,7 +146,7 @@ private IRubyObject initializeImpl(final ThreadContext context, BigInteger seria return this; } - private IRubyObject initializeImpl(byte[] derByteStream) throws IOException { + private IRubyObject initializeImpl(byte[] derByteStream) { this.bcCertId = CertID.getInstance(derByteStream); return this; @@ -171,8 +158,8 @@ public IRubyObject serial() { } @JRubyMethod(name = "issuer_name_hash") - public IRubyObject issuer_name_hash() { - Ruby runtime = getRuntime(); + public IRubyObject issuer_name_hash(ThreadContext context) { + Ruby runtime = context.runtime; String oidSym = ASN1.oid2Sym(runtime, getBCCertificateID().getHashAlgOID()); RubyString digestName = RubyString.newString(runtime, oidSym); @@ -183,17 +170,14 @@ public IRubyObject issuer_name_hash() { // a hash of a hash if we don't have the original issuer around. if (originalIssuer == null) { try { - return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, + return Digest.hexdigest(context, this, digestName, RubyString.newString(runtime, bcCertId.getIssuerNameHash().getEncoded("DER"))); } catch (IOException e) { throw newOCSPError(runtime, e); } } - else { - return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, - originalIssuer.getSubject().to_der(runtime.getCurrentContext())); - } + return Digest.hexdigest(context, this, digestName, originalIssuer.getSubject().to_der(context)); } // For whatever reason, the MRI Ruby tests appear to suggest that they compute the hexdigest hash @@ -202,23 +186,22 @@ public IRubyObject issuer_name_hash() { // is already computed and can't be reversed to get to the original key, so we just compute // a hash of a hash if we don't have the original issuer around. @JRubyMethod(name = "issuer_key_hash") - public IRubyObject issuer_key_hash() { - Ruby runtime = getRuntime(); + public IRubyObject issuer_key_hash(ThreadContext context) { + Ruby runtime = context.runtime; String oidSym = ASN1.oid2Sym(runtime, getBCCertificateID().getHashAlgOID()); RubyString digestName = RubyString.newString(runtime, oidSym); - if (originalIssuer == null) { - try { - return Digest.hexdigest(runtime.getCurrentContext(), this, RubyString.newString(runtime, oidSym), + try { + if (originalIssuer == null) { + return Digest.hexdigest(context, this, digestName, RubyString.newString(runtime, bcCertId.getIssuerKeyHash().getEncoded("DER"))); } - catch (IOException e) { - throw newOCSPError(runtime, e); - } + PKey key = (PKey) originalIssuer.public_key(context); + byte[] key_der = key.toASN1PublicInfo().toASN1Primitive().getEncoded(ASN1Encoding.DER); + return Digest.hexdigest(context, this, digestName, RubyString.newStringNoCopy(runtime, key_der)); } - else { - PKey key = (PKey)originalIssuer.public_key(runtime.getCurrentContext()); - return Digest.hexdigest(runtime.getCurrentContext(), this, digestName, key.to_der()); + catch (IOException e) { + throw newOCSPError(runtime, e); } } @@ -226,10 +209,7 @@ public IRubyObject issuer_key_hash() { public IRubyObject hash_algorithm() { Ruby runtime = getRuntime(); ASN1ObjectIdentifier oid = bcCertId.getHashAlgorithm().getAlgorithm(); - Integer nid = ASN1.oid2nid(runtime, oid); - String ln = ASN1.nid2ln(runtime, nid); - - return RubyString.newString(runtime, ln); + return RubyString.newString(runtime, ASN1.o2a(runtime, oid)); } @JRubyMethod(name = "cmp") diff --git a/src/main/java/org/jruby/ext/openssl/OCSPRequest.java b/src/main/java/org/jruby/ext/openssl/OCSPRequest.java index dfaacf4d..6f748478 100644 --- a/src/main/java/org/jruby/ext/openssl/OCSPRequest.java +++ b/src/main/java/org/jruby/ext/openssl/OCSPRequest.java @@ -82,7 +82,6 @@ import org.jruby.anno.JRubyMethod; import org.jruby.ext.openssl.x509store.X509AuxCertificate; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -98,19 +97,13 @@ */ public class OCSPRequest extends RubyObject { private static final long serialVersionUID = -4020616730425816999L; - - private static ObjectAllocator REQUEST_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new OCSPRequest(runtime, klass); - } - }; public OCSPRequest(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); } public static void createRequest(final Ruby runtime, final RubyModule _OCSP) { - RubyClass _request = _OCSP.defineClassUnder("Request", runtime.getObject(), REQUEST_ALLOCATOR); + RubyClass _request = _OCSP.defineClassUnder("Request", runtime.getObject(), (r, klass) -> new OCSPRequest(r, klass)); _request.defineAnnotatedMethods(OCSPRequest.class); } diff --git a/src/main/java/org/jruby/ext/openssl/OCSPResponse.java b/src/main/java/org/jruby/ext/openssl/OCSPResponse.java index 0f83dc03..8d3db3da 100644 --- a/src/main/java/org/jruby/ext/openssl/OCSPResponse.java +++ b/src/main/java/org/jruby/ext/openssl/OCSPResponse.java @@ -47,7 +47,6 @@ import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -62,12 +61,6 @@ */ public class OCSPResponse extends RubyObject { private static final long serialVersionUID = 5763247988029815198L; - - private static ObjectAllocator RESPONSE_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new OCSPResponse(runtime, klass); - } - }; public OCSPResponse(Ruby runtime, RubyClass metaClass) { super(runtime, metaClass); @@ -78,7 +71,7 @@ public OCSPResponse(Ruby runtime) { } public static void createResponse(final Ruby runtime, final RubyModule OCSP) { - RubyClass Response = OCSP.defineClassUnder("Response", runtime.getObject(), RESPONSE_ALLOCATOR); + RubyClass Response = OCSP.defineClassUnder("Response", runtime.getObject(), (r, klass) -> new OCSPResponse(r, klass)); Response.defineAnnotatedMethods(OCSPResponse.class); } diff --git a/src/main/java/org/jruby/ext/openssl/OCSPSingleResponse.java b/src/main/java/org/jruby/ext/openssl/OCSPSingleResponse.java index 0ab299fa..fed7a254 100644 --- a/src/main/java/org/jruby/ext/openssl/OCSPSingleResponse.java +++ b/src/main/java/org/jruby/ext/openssl/OCSPSingleResponse.java @@ -56,7 +56,6 @@ import org.jruby.RubyTime; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -71,15 +70,9 @@ */ public class OCSPSingleResponse extends RubyObject { private static final long serialVersionUID = 7947277768033100227L; - - private static ObjectAllocator SINGLERESPONSE_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new OCSPSingleResponse(runtime, klass); - } - }; public static void createSingleResponse(final Ruby runtime, final RubyModule _OCSP) { - RubyClass _request = _OCSP.defineClassUnder("SingleResponse", runtime.getObject(), SINGLERESPONSE_ALLOCATOR); + RubyClass _request = _OCSP.defineClassUnder("SingleResponse", runtime.getObject(), (r, klass) -> new OCSPSingleResponse(r, klass)); _request.defineAnnotatedMethods(OCSPSingleResponse.class); } diff --git a/src/main/java/org/jruby/ext/openssl/ObjectSupport.java b/src/main/java/org/jruby/ext/openssl/ObjectSupport.java index 0207fe2c..ef8df162 100644 --- a/src/main/java/org/jruby/ext/openssl/ObjectSupport.java +++ b/src/main/java/org/jruby/ext/openssl/ObjectSupport.java @@ -48,11 +48,21 @@ static RubyString inspect(final RubyBasicObject self, final List varia return RubyString.newString(runtime, inspect(runtime, self, variableList)); } + static RubyString inspect(final RubyBasicObject self, final CharSequence content) { + final Ruby runtime = self.getRuntime(); + final StringBuilder part = inspectHeader(self).append(' ').append(content).append('>'); + return RubyString.newString(runtime, part); + } + + private static StringBuilder inspectHeader(final RubyBasicObject self) { + final StringBuilder part = new StringBuilder(); + part.append("#<").append(self.getMetaClass().getRealClass().getName()); + return part; + } + private static StringBuilder inspect(final Ruby runtime, final RubyBasicObject self, final List variableList) { - final StringBuilder part = new StringBuilder(); - String cname = self.getMetaClass().getRealClass().getName(); - part.append("#<").append(cname).append(":0x"); + final StringBuilder part = inspectHeader(self).append(":0x"); part.append(Integer.toHexString(System.identityHashCode(self))); if (runtime.isInspecting(self)) { diff --git a/src/main/java/org/jruby/ext/openssl/OpenSSL.java b/src/main/java/org/jruby/ext/openssl/OpenSSL.java index d6123a4f..7330f650 100644 --- a/src/main/java/org/jruby/ext/openssl/OpenSSL.java +++ b/src/main/java/org/jruby/ext/openssl/OpenSSL.java @@ -23,13 +23,14 @@ */ package org.jruby.ext.openssl; -import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.Map; +import org.bouncycastle.util.Arrays; import org.jruby.*; import org.jruby.anno.JRubyMethod; import org.jruby.anno.JRubyModule; +import org.jruby.common.IRubyWarnings; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -88,18 +89,7 @@ public static void createOpenSSL(final Ruby runtime) { runtime.getLoadService().require("jopenssl/version"); - // MRI 1.8.7 : - // OpenSSL::VERSION: "1.0.0" - // OpenSSL::OPENSSL_VERSION: "OpenSSL 1.0.1c 10 May 2012" - // OpenSSL::OPENSSL_VERSION_NUMBER: 268439615 - // MRI 1.9.3 / 2.2.3 : - // OpenSSL::VERSION: "1.1.0" - // OpenSSL::OPENSSL_VERSION: "OpenSSL 1.0.1f 6 Jan 2014" - // OpenSSL::OPENSSL_VERSION_NUMBER: 268439663 - // OpenSSL::OPENSSL_LIBRARY_VERSION: ""OpenSSL 1.0.2d 9 Jul 2015" - // OpenSSL::FIPS: false - - final byte[] version = { '1','.','1','.','0' }; + final byte[] version = { '2','.','2','.','3' }; // C OpenSSL gem version _OpenSSL.setConstant("VERSION", StringHelper.newString(runtime, version)); @@ -107,13 +97,18 @@ public static void createOpenSSL(final Ruby runtime) { final RubyString jVERSION = JOpenSSL.getConstantAt("VERSION").asString(); final byte[] JRuby_OpenSSL_ = { 'J','R','u','b','y','-','O','p','e','n','S','S','L',' ' }; - final int OPENSSL_VERSION_NUMBER = 999999999; // NOTE: smt more useful? - ByteList OPENSSL_VERSION = new ByteList( jVERSION.getByteList().getRealSize() + JRuby_OpenSSL_.length ); OPENSSL_VERSION.setEncoding( jVERSION.getEncoding() ); OPENSSL_VERSION.append( JRuby_OpenSSL_ ); OPENSSL_VERSION.append( jVERSION.getByteList() ); + // < 3.0.0 until we have decent (full) compatibility + final byte OPENSSL_VERSION_MAJOR = 2; + final byte OPENSSL_VERSION_MINOR = 9; + final byte OPENSSL_VERSION_PATCH = 9; + final byte OPENSSL_VERSION_PRE_RELEASE = 0; + final int OPENSSL_VERSION_NUMBER = OPENSSL_VERSION_MAJOR << 28 | OPENSSL_VERSION_MINOR << 20 | OPENSSL_VERSION_PATCH << 4 | OPENSSL_VERSION_PRE_RELEASE; + final RubyString VERSION; _OpenSSL.setConstant("OPENSSL_VERSION", VERSION = runtime.newString(OPENSSL_VERSION)); _OpenSSL.setConstant("OPENSSL_VERSION_NUMBER", runtime.newFixnum(OPENSSL_VERSION_NUMBER)); @@ -162,6 +157,20 @@ public static IRubyObject Digest(final IRubyObject self, final IRubyObject name) return Digest.getConstantAt( name.asJavaString() ); } + @JRubyMethod(meta = true) + public static IRubyObject fixed_length_secure_compare(ThreadContext context, IRubyObject self, IRubyObject arg1, IRubyObject arg2) { + final ByteList str1 = arg1.convertToString().getByteList(); + final ByteList str2 = arg2.convertToString().getByteList(); + if (str1.length() != str2.length()) { + throw context.runtime.newArgumentError("inputs must be of equal length"); + } + return context.runtime.newBoolean( + Arrays.constantTimeAreEqual(str1.length(), + str1.unsafeBytes(), str1.begin(), + str2.unsafeBytes(), str2.begin() + )); + } + // API "stubs" in JRuby-OpenSSL : @JRubyMethod(meta = true) @@ -174,11 +183,16 @@ public static IRubyObject check_func(final IRubyObject self, final IRubyObject[] return self.getRuntime().getNil(); // no-op in JRuby-OpenSSL } - // Added in 2.0; not masked because it does nothing anyway (there's no reader in MRI) + @JRubyMethod(name = "fips_mode", meta = true) + public static IRubyObject get_fips_mode(ThreadContext context, IRubyObject self) { + warn(context, "FIPS mode not implemented on JRuby-OpenSSL"); + return context.nil; + } + @JRubyMethod(name = "fips_mode=", meta = true) public static IRubyObject set_fips_mode(ThreadContext context, IRubyObject self, IRubyObject value) { if ( value.isTrue() ) { - warn(context, "FIPS mode not supported on JRuby-OpenSSL"); + warn(context, "FIPS mode not implemented on JRuby-OpenSSL"); } return value; } @@ -186,18 +200,14 @@ public static IRubyObject set_fips_mode(ThreadContext context, IRubyObject self, // internal (package-level) helpers : /** - * PRIMARILY MEANT FOR TESTING ONLY, USAGE IS DISCOURAGED! - * @see org.jruby.ext.openssl.util.CryptoSecurity + * @deprecated */ @JRubyMethod(name = "_disable_security_restrictions!", visibility = Visibility.PRIVATE, meta = true) public static IRubyObject _disable_security_restrictions(ThreadContext context, IRubyObject self) { - Boolean unrestrict = org.jruby.ext.openssl.util.CryptoSecurity.unrestrictSecurity(); - Boolean allPerm = org.jruby.ext.openssl.util.CryptoSecurity.setAllPermissionPolicy(); - if ( unrestrict == null || allPerm == null ) return context.nil; - return context.runtime.newBoolean( unrestrict && allPerm ); + warnDeprecated(context, "OpenSSL._disable_security_restrictions! is deprecated for removal"); + return context.nil; } - private static boolean debug; // on by default, warnings can be disabled using -Djruby.openssl.warn=false @@ -235,6 +245,14 @@ public static void debug(final Ruby runtime, final CharSequence msg, final Throw if ( isDebug(runtime) ) runtime.getOut().println(msg.toString() + ' ' + e); } + public static void debugStackTrace(final Ruby runtime, final CharSequence msg, final Throwable e) { + if ( isDebug(runtime) ) { + synchronized (runtime.getOut()) { + runtime.getOut().print(msg.toString() + ' '); + e.printStackTrace(runtime.getOut()); + } + } + } static void warn(final ThreadContext context, final CharSequence msg) { if ( warn ) warn(context, RubyString.newString(context.runtime, msg)); } @@ -247,6 +265,12 @@ static void warn(final ThreadContext context, final IRubyObject msg) { if ( warn ) context.runtime.getModule("OpenSSL").callMethod(context, "warn", msg); } + public static void warnDeprecated(final ThreadContext context, final CharSequence msg) { + if ( warn ) { + context.runtime.getWarnings().warn(IRubyWarnings.ID.DEPRECATED_METHOD, msg.toString()); + } + } + private static String javaVersion(final String def, final int len) { String javaVersion = SafePropertyAccessor.getProperty("java.version", def); if ( "0".equals(javaVersion) ) javaVersion = "1.7.0"; // Android @@ -307,7 +331,6 @@ static SecureRandom getSecureRandom(final Ruby runtime) { return getSecureRandom(runtime, false); } - static SecureRandom getSecureRandom(final Ruby runtime, final boolean nullByDefault) { if ( tryContextSecureRandom ) { SecureRandom random = getSecureRandomFrom(runtime.getCurrentContext()); @@ -316,19 +339,21 @@ static SecureRandom getSecureRandom(final Ruby runtime, final boolean nullByDefa return nullByDefault ? null : new SecureRandom(); } - static SecureRandom getSecureRandomFrom(final ThreadContext context) { + static SecureRandom getSecureRandom(final ThreadContext context) { if ( tryContextSecureRandom ) { - try { - SecureRandom random = context.secureRandom; - if (random == null) { // public SecureRandom getSecureRandom() on 9K - random = (SecureRandom) context.getClass().getMethod("getSecureRandom").invoke(context); - } - return random; - } - catch (Throwable ex) { - tryContextSecureRandom = false; - debug(context.runtime, "JRuby-OpenSSL failed to retrieve secure random from thread-context", ex); - } + SecureRandom random = getSecureRandomFrom(context); + if ( random != null ) return random; + } + return new SecureRandom(); + } + + private static SecureRandom getSecureRandomFrom(final ThreadContext context) { + try { + return context.getSecureRandom(); + } + catch (Throwable ex) { + tryContextSecureRandom = false; + debug(context.runtime, "JRuby-OpenSSL failed to retrieve secure random from thread-context", ex); } return null; } @@ -343,11 +368,7 @@ static IRubyObject to_der_if_possible(final ThreadContext context, IRubyObject o // - static String bcExceptionMessage(NoSuchProviderException ex) { - return "You need to configure JVM/classpath to enable BouncyCastle Security Provider: " + ex; - } - - static String bcExceptionMessage(NoClassDefFoundError ex) { + static String bcExceptionMessage(Throwable ex) { return "You need to configure JVM/classpath to enable BouncyCastle Security Provider: " + ex; } diff --git a/src/main/java/org/jruby/ext/openssl/OpenSSLReal.java b/src/main/java/org/jruby/ext/openssl/OpenSSLReal.java deleted file mode 100644 index bda68ebc..00000000 --- a/src/main/java/org/jruby/ext/openssl/OpenSSLReal.java +++ /dev/null @@ -1,108 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - * Version: EPL 1.0/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Eclipse Public - * License Version 1.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.eclipse.org/legal/epl-v10.html - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * Copyright (C) 2006 Ola Bini - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the EPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the EPL, the GPL or the LGPL. - ***** END LICENSE BLOCK *****/ -package org.jruby.ext.openssl; - -import java.security.GeneralSecurityException; -import java.security.NoSuchProviderException; -import java.security.Provider; - -/** - * @deprecated no longer used - * @see OpenSSL - * @author Ola Bini - */ -public class OpenSSLReal { - - private OpenSSLReal() { /* no instances */ } - - @Deprecated - public interface Runnable { - void run() throws GeneralSecurityException; - } - - public interface Callable { - T call() throws GeneralSecurityException; - } - - /** - * Run a block of code with 'BC' provider installed. - * - * @deprecated No longer used within the JRuby-OpenSSL code-base, please avoid! - * - * @param block - * @throws GeneralSecurityException - */ - @Deprecated - public static void doWithBCProvider(final Runnable block) throws GeneralSecurityException { - getWithBCProvider(new Callable() { - public Void call() throws GeneralSecurityException { - block.run(); return null; - } - }); - } - - /** - * Adds BouncyCastleProvider if it's allowed (no security exceptions thrown) - * and runs the block of code. Once added the provider will stay registered - * within java.security.Security API. This might lead to memory - * leaks e.g. when the Ruby runtime that loaded BC is teared down. - * - * Removing the 'BC' provided (once the block run) can remove pre-installed - * or another runtime-added BC provider thus causing unknown runtime errors. - * - * @deprecated No longer used within the JRuby-OpenSSL code-base, please avoid! - * - * @param - * @param block - * @return - * @throws GeneralSecurityException - */ - @Deprecated - public static T getWithBCProvider(final Callable block) throws GeneralSecurityException { - try { - final Provider provider = SecurityHelper.getSecurityProvider(); // BC - if (provider != null && java.security.Security.getProvider(provider.getName()) == null) { - java.security.Security.addProvider(provider); - } - return block.call(); - } catch (NoSuchProviderException nspe) { - throw new GeneralSecurityException(bcExceptionMessage(nspe), nspe); - } catch (Exception e) { - throw new GeneralSecurityException(e.getMessage(), e); - } - } - - public static String bcExceptionMessage(NoSuchProviderException e) { - return OpenSSL.bcExceptionMessage(e); - } - - public static String bcExceptionMessage(NoClassDefFoundError e) { - return OpenSSL.bcExceptionMessage(e); - } - -} \ No newline at end of file diff --git a/src/main/java/org/jruby/ext/openssl/PKCS7.java b/src/main/java/org/jruby/ext/openssl/PKCS7.java index 09e1d66f..fb1f25be 100644 --- a/src/main/java/org/jruby/ext/openssl/PKCS7.java +++ b/src/main/java/org/jruby/ext/openssl/PKCS7.java @@ -29,8 +29,10 @@ import java.io.IOException; import java.io.StringWriter; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashSet; import java.util.List; @@ -39,6 +41,7 @@ import java.security.cert.CertificateEncodingException; import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1UTCTime; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -49,6 +52,7 @@ import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; +import org.jruby.RubyTime; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; @@ -738,8 +742,20 @@ public IRubyObject serial() { @JRubyMethod public IRubyObject signed_time(final ThreadContext context) { - warn(context, "WARNING: unimplemented method called: OpenSSL::PKCS7::SignerInfo#signed_time"); - return context.runtime.getNil(); + ASN1Encodable asn1obj = info.getSignedAttribute(ASN1Registry.NID_pkcs9_signingTime); + if (asn1obj == null) { + throw newPKCS7Error(context.runtime, "no signing time attribute"); + } + if (asn1obj instanceof ASN1UTCTime) { + final Date adjusted; + try { + adjusted = ((ASN1UTCTime) asn1obj).getAdjustedDate(); + } catch (ParseException ex) { + throw newPKCS7Error(context.runtime, ex); + } + return RubyTime.newTime(context.runtime, adjusted.getTime()); + } + return context.nil; } } diff --git a/src/main/java/org/jruby/ext/openssl/PKey.java b/src/main/java/org/jruby/ext/openssl/PKey.java index e162835f..51b99b48 100644 --- a/src/main/java/org/jruby/ext/openssl/PKey.java +++ b/src/main/java/org/jruby/ext/openssl/PKey.java @@ -27,20 +27,23 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ext.openssl; -import java.io.ByteArrayInputStream; +import java.io.Console; import java.io.IOException; -import java.io.InputStreamReader; import java.io.StringReader; import java.math.BigInteger; import java.security.*; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyModule; @@ -48,15 +51,17 @@ import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; +import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.Visibility; +import org.jruby.util.ByteList; +import org.jruby.ext.openssl.impl.CipherSpec; import org.jruby.ext.openssl.x509store.PEMInputOutput; + import static org.jruby.ext.openssl.OpenSSL.*; -import org.jruby.ext.openssl.impl.CipherSpec; -import org.jruby.util.ByteList; /** * @author Ola Bini @@ -84,6 +89,20 @@ public static RaiseException newPKeyError(Ruby runtime, String message) { return Utils.newError(runtime, (RubyClass) _PKey(runtime).getConstantAt("PKeyError"), message); } + public static PKey newInstance(final Ruby runtime, final PublicKey publicKey) { + if (publicKey instanceof RSAPublicKey) { + return new PKeyRSA(runtime, (RSAPublicKey) publicKey); + } + if (publicKey instanceof DSAPublicKey) { + return new PKeyDSA(runtime, (DSAPublicKey) publicKey); + } + if (publicKey instanceof ECPublicKey) { + return new PKeyEC(runtime, publicKey); + } + assert publicKey != null; + throw runtime.newNotImplementedError("public key algorithm: " + (publicKey != null ? publicKey.getAlgorithm() : "nil")); + } + static RubyModule _PKey(final Ruby runtime) { return (RubyModule) runtime.getModule("OpenSSL").getConstantAt("PKey"); } @@ -105,74 +124,71 @@ public static IRubyObject read(final ThreadContext context, IRubyObject recv, IR pass = args[1].isNil() ? null : args[1].toString().toCharArray(); } - final byte[] input = StringHelper.readX509PEM(context, data); - KeyPair key = null; + final RubyString str = readInitArg(context, data); + KeyPair keyPair; // d2i_PrivateKey_bio try { - key =org.jruby.ext.openssl.impl.PKey.readPrivateKey(input); - } catch (IOException ioe) { - // ignore - } catch (GeneralSecurityException gse) { - // ignore + keyPair = readPrivateKey(str, pass); + } catch (IOException e) { + debugStackTrace(runtime, "PKey readPrivateKey", e); /* ignore */ + keyPair = null; } // PEM_read_bio_PrivateKey - if (key == null) { - try { - key = PEMInputOutput.readPrivateKey(new InputStreamReader(new ByteArrayInputStream(input)), pass); - } catch (IOException ioe) { - // ignore - } - } - if (key != null) { - final String alg = getAlgorithm(key); + if (keyPair != null) { + final String alg = getAlgorithm(keyPair); if ( "RSA".equals(alg) ) { - return new PKeyRSA(runtime, _PKey(runtime).getClass("RSA"), - (RSAPrivateCrtKey) key.getPrivate(), (RSAPublicKey) key.getPublic() - ); + return new PKeyRSA(runtime, _PKey(runtime).getClass("RSA"), (RSAPrivateCrtKey) keyPair.getPrivate(), (RSAPublicKey) keyPair.getPublic()); } if ( "DSA".equals(alg) ) { - return new PKeyDSA(runtime, _PKey(runtime).getClass("DSA"), - (DSAPrivateKey) key.getPrivate(), (DSAPublicKey) key.getPublic() - ); + return new PKeyDSA(runtime, _PKey(runtime).getClass("DSA"), (DSAPrivateKey) keyPair.getPrivate(), (DSAPublicKey) keyPair.getPublic()); } - if ( "ECDSA".equals(alg) ) { - return new PKeyEC(runtime, _PKey(runtime).getClass("EC"), - (PrivateKey) key.getPrivate(), (PublicKey) key.getPublic() - ); + if ( "EC".equals(alg) || "ECDSA".equals(alg) ) { // Sun vs BC provider naming + return new PKeyEC(runtime, _PKey(runtime).getClass("EC"), keyPair.getPrivate(), keyPair.getPublic()); } + debug(runtime, "PKey readPrivateKey unexpected key pair algorithm: " + alg); } PublicKey pubKey = null; + try { + pubKey = PEMInputOutput.readRSAPublicKey(new StringReader(str.toString()), null); + if (pubKey != null) return new PKeyRSA(runtime, (RSAPublicKey) pubKey); + } catch (IOException e) { + debugStackTrace(runtime, "PKey readRSAPublicKey", e); /* ignore */ + } + try { + pubKey = PEMInputOutput.readDSAPublicKey(new StringReader(str.toString()), null); + if (pubKey != null) return new PKeyDSA(runtime, (DSAPublicKey) pubKey); + } catch (IOException e) { + debugStackTrace(runtime, "PKey readDSAPublicKey", e); /* ignore */ + } + + final byte[] input = StringHelper.readX509PEM(context, str); // d2i_PUBKEY_bio try { pubKey = org.jruby.ext.openssl.impl.PKey.readPublicKey(input); - } catch (IOException ioe) { - // ignore - } catch (GeneralSecurityException gse) { - // ignore + } catch (IOException e) { + debugStackTrace(runtime, "PKey readPublicKey", e); /* ignore */ } // PEM_read_bio_PUBKEY if (pubKey == null) { try { - pubKey = PEMInputOutput.readPubKey(new InputStreamReader(new ByteArrayInputStream(input))); - } catch (IOException ioe) { - // ignore + pubKey = PEMInputOutput.readPubKey(new StringReader(str.toString())); + } catch (IOException e) { + debugStackTrace(runtime, "PKey readPubKey", e); /* ignore */ } } - if (pubKey != null) { - if ( "RSA".equals(pubKey.getAlgorithm()) ) { - return new PKeyRSA(runtime, (RSAPublicKey) pubKey); - } - if ( "DSA".equals(pubKey.getAlgorithm()) ) { - return new PKeyDSA(runtime, (DSAPublicKey) pubKey); - } - if ( "ECDSA".equals(pubKey.getAlgorithm()) ) { - return new PKeyEC(runtime, pubKey); - } + if (pubKey instanceof RSAPublicKey) { + return new PKeyRSA(runtime, (RSAPublicKey) pubKey); + } + if (pubKey instanceof DSAPublicKey) { + return new PKeyDSA(runtime, (DSAPublicKey) pubKey); + } + if (pubKey instanceof ECPublicKey) { + return new PKeyEC(runtime, pubKey); } - throw runtime.newArgumentError("Could not parse PKey"); + throw newPKeyError(runtime, "Could not parse PKey: unsupported"); } private static String getAlgorithm(final KeyPair key) { @@ -180,7 +196,6 @@ private static String getAlgorithm(final KeyPair key) { if ( key.getPublic() != null ) return key.getPublic().getAlgorithm(); return null; } - } public PKey(Ruby runtime, RubyClass type) { @@ -199,16 +214,13 @@ public IRubyObject initialize(ThreadContext context) { public String getAlgorithm() { return "NONE"; } + public String getKeyType() { return getAlgorithm(); } + public boolean isPrivateKey() { return getPrivateKey() != null; } public abstract RubyString to_der() ; - public abstract RubyString to_pem(final IRubyObject[] args) ; - - @Deprecated - public RubyString export(final IRubyObject[] args) { - return to_pem(args); - } + public abstract RubyString to_pem(ThreadContext context, final IRubyObject[] args) ; @JRubyMethod(name = "sign") public IRubyObject sign(IRubyObject digest, IRubyObject data) { @@ -224,6 +236,35 @@ public IRubyObject sign(IRubyObject digest, IRubyObject data) { } } + public ASN1Primitive toASN1PublicInfo() throws IOException { + ASN1InputStream input = new ASN1InputStream(to_der().getBytes()); + + ASN1Primitive data = input.readObject(); + if (data instanceof ASN1Sequence) { + return ((ASN1Sequence) data).getObjectAt(1).toASN1Primitive(); + } + return data; + } + + @Override + public Object toJava(final Class target) { + if (PrivateKey.class.isAssignableFrom(target)) { + final PrivateKey key = getPrivateKey(); + if (key == null) { + throw getRuntime().newRuntimeError("private key not available, to convert to " + target); + } + if (target.isInstance(key)) return key; + throw getRuntime().newTypeError("cannot convert private key of type " + key.getClass() + " to " + target); + } + if (target.isAssignableFrom(PublicKey.class) || Key.class.isAssignableFrom(target)) { + // default is public key, also want to_java() as well as to_java(java.lang.Object) to end up here + final PublicKey key = getPublicKey(); + if (target.isInstance(key)) return key; + throw getRuntime().newTypeError("cannot convert public key of type " + key.getClass() + " to " + target); + } + return super.toJava(target); + } + static ByteList sign(final String signAlg, final PrivateKey privateKey, final ByteList data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { Signature signature = SecurityHelper.getSignature(signAlg); @@ -274,7 +315,7 @@ static SecureRandom getSecureRandom(final Ruby runtime) { return OpenSSL.getSecureRandom(runtime); } - // shared Helpers for PKeyRSA / PKEyDSA : + // shared Helpers for PKeyRSA / PKeyDSA : protected PrivateKey tryPKCS8EncodedKey(final Ruby runtime, final KeyFactory keyFactory, final byte[] encodedKey) { try { @@ -335,20 +376,22 @@ protected PublicKey tryX509EncodedKey(final Ruby runtime, final KeyFactory keyFa } protected static void addSplittedAndFormatted(StringBuilder result, BigInteger value) { - String v = value.toString(16); - if ((v.length() % 2) != 0) { - v = "0" + v; - } - String sep = ""; + addSplittedAndFormatted(result, value.toString(16)); + } + + static void addSplittedAndFormatted(StringBuilder result, CharSequence v) { + if ((v.length() % 2) != 0) v = "0" + v; + + char sep = '\0'; for (int i = 0; i < v.length(); i += 2) { result.append(sep); if ((i % 30) == 0) { result.append("\n "); } - result.append(v.substring(i, i + 2)); - sep = ":"; + result.append(v, i, i + 2); + sep = ':'; } - result.append("\n"); + result.append('\n'); } protected static CipherSpec cipherSpec(final IRubyObject cipher) { @@ -359,6 +402,7 @@ protected static CipherSpec cipherSpec(final IRubyObject cipher) { return null; } + @Deprecated protected static char[] password(final IRubyObject pass) { if ( pass != null && ! pass.isNil() ) { return pass.toString().toCharArray(); @@ -366,17 +410,33 @@ protected static char[] password(final IRubyObject pass) { return null; } + protected static char[] password(final ThreadContext context, IRubyObject pass, final Block block) { + if (pass != null && !pass.isNil()) { // argument takes precedence (instead of block) + return pass.toString().toCharArray(); + } + if (block != null && block.isGiven()) { + return password(context, block.call(context), null); + } + return null; + } + protected static char[] passwordPrompt(final ThreadContext context) { return passwordPrompt(context, "Enter PEM pass phrase:"); } protected static char[] passwordPrompt(final ThreadContext context, final String prompt) { + Console console = System.console(); + if (console != null) { + return console.readPassword(prompt); + } + + // fall back on simple IO, but may be broken (jruby/jruby#5588) final RubyModule Kernel = context.runtime.getKernel(); // NOTE: just a fast and simple print && gets - hopefully better than nothing! Kernel.callMethod("print", context.runtime.newString(prompt)); final RubyString gets = Kernel.callMethod(context, "gets").convertToString(); gets.chomp_bang(context); - return gets.toString().toCharArray(); + return gets.decodeString().toCharArray(); } protected static boolean ttySTDIN(final ThreadContext context) { @@ -389,12 +449,12 @@ protected static boolean ttySTDIN(final ThreadContext context) { catch (RaiseException ex) { return false; } } - static Object readPrivateKey(final String str, final char[] passwd) + static KeyPair readPrivateKey(final String str, final char[] passwd) throws PEMInputOutput.PasswordRequiredException, IOException { return PEMInputOutput.readPrivateKey(new StringReader(str), passwd); } - static Object readPrivateKey(final RubyString str, final char[] passwd) + static KeyPair readPrivateKey(final RubyString str, final char[] passwd) throws PEMInputOutput.PasswordRequiredException, IOException { return readPrivateKey(str.toString(), passwd); } diff --git a/src/main/java/org/jruby/ext/openssl/PKeyDH.java b/src/main/java/org/jruby/ext/openssl/PKeyDH.java index dd5f4ec9..d05dea65 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyDH.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyDH.java @@ -97,16 +97,11 @@ public static RaiseException newDHError(Ruby runtime, String message) { private transient volatile BigInteger dh_y; private transient volatile BigInteger dh_x; - // FIXME! need to figure out what it means in MRI/OSSL code to - // claim a DH is(/has) private if an engine is present -- doesn't really - // map to Java implementation. - - //private volatile boolean haveEngine; - public PKeyDH(Ruby runtime, RubyClass clazz) { super(runtime, clazz); } + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; @@ -120,6 +115,21 @@ public IRubyObject initialize_copy(final IRubyObject original) { return this; } + @JRubyMethod(name = "generate", meta = true, rest = true) + public static IRubyObject generate(final ThreadContext context, IRubyObject self, IRubyObject[] args) { + final Ruby runtime = context.runtime; + final int g; + if (Arity.checkArgumentCount(runtime, args, 1, 2) == 2) { + g = RubyNumeric.num2int(args[1]); + } else { + g = 2; + } + + PKeyDH pkey = new PKeyDH(runtime, _PKey(runtime).getClass("DH")); + pkey.generate(runtime, args[0], g); + return pkey; + } + @JRubyMethod(name="initialize", rest=true, visibility = Visibility.PRIVATE) public synchronized IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { final Ruby runtime = context.runtime; @@ -150,28 +160,29 @@ public synchronized IRubyObject initialize(final ThreadContext context, final IR throw runtime.newIOErrorFromException(e); } } else { - int bits = RubyNumeric.fix2int(arg0); - // g defaults to 2 - int gval = argc == 2 ? RubyNumeric.fix2int(args[1]) : 2; - BigInteger p; - try { - p = generateP(bits, gval); - } - catch(IllegalArgumentException e) { - throw runtime.newArgumentError(e.getMessage()); - } - BigInteger g = BigInteger.valueOf(gval); - BigInteger x = generateX(p); - BigInteger y = generateY(p, g, x); - this.dh_p = p; - this.dh_g = g; - this.dh_x = x; // private key - this.dh_y = y; // public key + generate(runtime, arg0, argc == 2 ? RubyNumeric.num2int(args[1]) : 2); // g defaults to 2 } } return this; } + private void generate(final Ruby runtime, final IRubyObject bits, final int gval) { + BigInteger p; + try { + p = generateP(RubyNumeric.num2int(bits), gval); + } + catch(IllegalArgumentException e) { + throw runtime.newArgumentError(e.getMessage()); + } + BigInteger g = BigInteger.valueOf(gval); + BigInteger x = generateX(p); + BigInteger y = generateY(p, g, x); + this.dh_p = p; + this.dh_g = g; + this.dh_x = x; // private key + this.dh_y = y; // public key + } + public static BigInteger generateP(int bits, int g) { // FIXME? I'm following algorithms used in OpenSSL, could use JCE provider instead. @@ -202,9 +213,6 @@ public static BigInteger generateX(BigInteger p, int limit) { BigInteger x; SecureRandom secureRandom = new SecureRandom(); // adapting algorithm from org.bouncycastle.crypto.generators.DHKeyGeneratorHelper, - // which seems a little stronger (?) than OpenSSL's (OSSL just generates a random, - // while BC generates a random potential prime [for limit > 0], though it's not - // subject to Miller-Rabin [certainty = 0], but is subject to other constraints) // see also [ossl]/crypto/dh/dh_key.c #generate_key if (limit == 0) { final BigInteger pSub2 = p.subtract(TWO); @@ -213,8 +221,7 @@ public static BigInteger generateX(BigInteger p, int limit) { } while (x.equals(BigInteger.ZERO)); } else { do { - // generate potential prime, though with 0 certainty (no Miller-Rabin tests) - x = new BigInteger(limit, 0, secureRandom); + x = new BigInteger(limit, secureRandom); } while (x.equals(BigInteger.ZERO)); } return x; @@ -229,10 +236,6 @@ public static BigInteger generateY(BigInteger p, BigInteger g, BigInteger x) { return g.modPow(x, p); } - public static BigInteger generateY(BigInteger p, int g, BigInteger x) { - return generateY(p, BigInteger.valueOf(g), x); - } - @JRubyMethod(name = "generate_key!") public synchronized IRubyObject generate_key() { BigInteger p, g, x, y; @@ -275,20 +278,17 @@ public RubyBoolean public_p() { @Override public boolean isPrivateKey() { - return dh_x != null /* || haveEngine */; + return dh_x != null; } @JRubyMethod(name = "private?") public RubyBoolean private_p() { - // FIXME! need to figure out what it means in MRI/OSSL code to - // claim a DH is private if an engine is present -- doesn't really - // map to Java implementation. return getRuntime().newBoolean(isPrivateKey()); } @Override @JRubyMethod(name = { "to_pem", "to_s" }, alias = "export", rest = true) - public RubyString to_pem(final IRubyObject[] args) { + public RubyString to_pem(ThreadContext context, final IRubyObject[] args) { //Arity.checkArgumentCount(getRuntime(), args, 0, 2); //CipherSpec spec = null; char[] passwd = null; @@ -377,6 +377,21 @@ public synchronized IRubyObject set_g(IRubyObject arg) { return arg; } + @JRubyMethod(name = "q") + public IRubyObject q(final ThreadContext context) { + return context.nil; + } + + @JRubyMethod + public IRubyObject set_pqg(final ThreadContext context, IRubyObject p, IRubyObject q, IRubyObject g) { + set_p(p); + if (!q.isNil()) { + OpenSSL.warn(context, "JRuby-OpenSSL does not support setting q param on " + inspect()); + } + set_g(g); + return this; + } + // don't need synchronized as value is volatile @JRubyMethod(name = "pub_key") public IRubyObject pub_key() { @@ -417,6 +432,13 @@ public synchronized IRubyObject set_priv_key(IRubyObject arg) { return arg; } + @JRubyMethod + public IRubyObject set_key(final ThreadContext context, IRubyObject pub_key, IRubyObject priv_key) { + set_pub_key(pub_key); + set_priv_key(priv_key); + return this; + } + private IRubyObject newBN(BigInteger value) { if (value == null) return getRuntime().getNil(); return BN.newBN(getRuntime(), value); diff --git a/src/main/java/org/jruby/ext/openssl/PKeyDSA.java b/src/main/java/org/jruby/ext/openssl/PKeyDSA.java index 7063a771..83c6d1fd 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyDSA.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyDSA.java @@ -34,16 +34,19 @@ import java.math.BigInteger; import java.security.*; import java.security.interfaces.DSAKey; +import java.security.interfaces.DSAParams; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; +import org.bouncycastle.asn1.ASN1Primitive; import org.jruby.Ruby; import org.jruby.RubyBoolean; import org.jruby.RubyClass; import org.jruby.RubyFixnum; +import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyString; @@ -52,6 +55,7 @@ import org.jruby.ext.openssl.impl.CipherSpec; import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.runtime.Arity; +import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.ThreadContext; @@ -61,8 +65,9 @@ import static org.jruby.ext.openssl.OpenSSL.*; import static org.jruby.ext.openssl.impl.PKey.readDSAPrivateKey; import static org.jruby.ext.openssl.impl.PKey.readDSAPublicKey; +import static org.jruby.ext.openssl.impl.PKey.toASN1Primitive; import static org.jruby.ext.openssl.impl.PKey.toDerDSAKey; -import static org.jruby.ext.openssl.PKey._PKey; +import static org.jruby.ext.openssl.impl.PKey.toDerDSAPublicKey; /** * @author Ola Bini @@ -112,6 +117,7 @@ public PKeyDSA(Ruby runtime, RubyClass type, DSAPrivateKey privKey, DSAPublicKey private transient volatile BigInteger dsa_q; private transient volatile BigInteger dsa_g; + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; @@ -165,29 +171,23 @@ private static PKeyDSA dsaGenerate(final Ruby runtime, } } - static PKeyDSA newInstance(final Ruby runtime, final PublicKey publicKey) { - //if ( publicKey instanceof DSAPublicKey ) { - return new PKeyDSA(runtime, (DSAPublicKey) publicKey); - //} - } - @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) - public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { + public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, final Block block) { final Ruby runtime = context.runtime; if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 0 ) { this.privateKey = null; this.publicKey = null; return this; } - IRubyObject arg = args[0]; IRubyObject pass = null; - if ( args.length > 1 ) pass = args[1]; + IRubyObject arg = args[0]; + IRubyObject arg1 = args.length > 1 ? args[1] : null; // password (String) if ( arg instanceof RubyFixnum ) { int keySize = RubyNumeric.fix2int((RubyFixnum) arg); return dsaGenerate(context.runtime, this, keySize); } - final char[] passwd = password(pass); + final char[] passwd = password(context, arg1, block); final RubyString str = readInitArg(context, arg); final String strJava = str.toString(); @@ -237,7 +237,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (InvalidKeySpecException e) { debug(runtime, "PKeyDSA could not read private key", e); } - catch (IOException e) { debug(runtime, "PKeyDSA could not read private key", e); } + catch (IOException e) { debugStackTrace(runtime, "PKeyDSA could not read private key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyDSA could not read private key", e); else debugStackTrace(runtime, e); @@ -249,7 +249,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (InvalidKeySpecException e) { debug(runtime, "PKeyDSA could not read public key", e); } - catch (IOException e) { debug(runtime, "PKeyDSA could not read public key", e); } + catch (IOException e) { debugStackTrace(runtime, "PKeyDSA could not read public key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyDSA could not read public key", e); else debugStackTrace(runtime, e); @@ -300,6 +300,21 @@ public RubyBoolean private_p() { return privateKey != null ? getRuntime().getTrue() : getRuntime().getFalse(); } + @JRubyMethod(name = "public_to_der") + public RubyString public_to_der(ThreadContext context) { + final byte[] bytes; + try { + bytes = toDerDSAPublicKey(publicKey); + } + catch (NoClassDefFoundError e) { + throw newDSAError(getRuntime(), bcExceptionMessage(e)); + } + catch (IOException e) { + throw newDSAError(getRuntime(), e.getMessage(), e); + } + return StringHelper.newString(context.runtime, bytes); + } + @Override @JRubyMethod(name = "to_der") public RubyString to_der() { @@ -316,6 +331,11 @@ public RubyString to_der() { return StringHelper.newString(getRuntime(), bytes); } + @Override + public ASN1Primitive toASN1PublicInfo() { + return toASN1Primitive(publicKey); + } + @JRubyMethod public RubyString to_text() { StringBuilder result = new StringBuilder(); @@ -341,15 +361,40 @@ public PKeyDSA public_key() { return new PKeyDSA(getRuntime(), this.publicKey); } + @JRubyMethod + public IRubyObject params(final ThreadContext context) { + final Ruby runtime = context.runtime; + RubyHash hash = RubyHash.newHash(runtime); + if (publicKey != null) { + if (publicKey.getParams() != null) { + setParams(context, runtime, hash, publicKey.getParams()); + } + hash.op_aset(context, runtime.newString("pub_key"), BN.newBN(runtime, publicKey.getY())); + } + if (privateKey != null) { + if (publicKey == null && privateKey.getParams() != null) { + setParams(context, runtime, hash, privateKey.getParams()); + } + hash.op_aset(context, runtime.newString("priv_key"), BN.newBN(runtime, privateKey.getX())); + } + return hash; + } + + private static void setParams(ThreadContext context, Ruby runtime, RubyHash hash, DSAParams params) { + hash.op_aset(context, runtime.newString("p"), BN.newBN(runtime, params.getP())); + hash.op_aset(context, runtime.newString("q"), BN.newBN(runtime, params.getQ())); + hash.op_aset(context, runtime.newString("g"), BN.newBN(runtime, params.getG())); + } + @Override @JRubyMethod(name = { "to_pem", "to_s" }, alias = "export", rest = true) - public RubyString to_pem(final IRubyObject[] args) { - Arity.checkArgumentCount(getRuntime(), args, 0, 2); + public RubyString to_pem(final ThreadContext context, final IRubyObject[] args) { + Arity.checkArgumentCount(context.runtime, args, 0, 2); CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); - if ( args.length > 1 ) passwd = password(args[1]); + if ( args.length > 1 ) passwd = password(context, args[1], null); } try { @@ -360,13 +405,28 @@ public RubyString to_pem(final IRubyObject[] args) { else { PEMInputOutput.writeDSAPublicKey(writer, publicKey); } - return RubyString.newString(getRuntime(), writer.getBuffer()); + return RubyString.newString(context.runtime, writer.getBuffer()); } catch (NoClassDefFoundError ncdfe) { - throw newDSAError(getRuntime(), bcExceptionMessage(ncdfe)); + throw newDSAError(context.runtime, bcExceptionMessage(ncdfe)); } catch (IOException e) { - throw newDSAError(getRuntime(), e.getMessage(), e); + throw newDSAError(context.runtime, e.getMessage(), e); + } + } + + @JRubyMethod + public RubyString public_to_pem(ThreadContext context) { + try { + final StringWriter writer = new StringWriter(); + PEMInputOutput.writeDSAPublicKey(writer, publicKey); + return RubyString.newString(context.runtime, writer.getBuffer()); + } + catch (NoClassDefFoundError ncdfe) { + throw newDSAError(context.runtime, bcExceptionMessage(ncdfe)); + } + catch (IOException e) { + throw newDSAError(context.runtime, e.getMessage(), e); } } @@ -407,6 +467,11 @@ public IRubyObject sysverify(IRubyObject data, IRubyObject sign) { } } + @JRubyMethod + public IRubyObject oid() { + return getRuntime().newString("DSA"); + } + private DSAKey getDsaKey() { DSAKey result; return (result = publicKey) != null ? result : privateKey; @@ -417,11 +482,11 @@ private IRubyObject toBN(BigInteger value) { } private synchronized BigInteger getP() { + if (dsa_p != null) return dsa_p; + DSAKey key = getDsaKey(); - if (key != null) { - return key.getParams().getP(); - } - return dsa_p; + if (key != null) return key.getParams().getP(); + return null; } @JRubyMethod(name = "p") @@ -435,11 +500,11 @@ public synchronized IRubyObject set_p(IRubyObject p) { } private synchronized BigInteger getQ() { + if (dsa_q != null) return dsa_q; + DSAKey key = getDsaKey(); - if (key != null) { - return key.getParams().getQ(); - } - return dsa_q; + if (key != null) return key.getParams().getQ(); + return null; } @JRubyMethod(name = "q") @@ -453,11 +518,11 @@ public synchronized IRubyObject set_q(IRubyObject q) { } private synchronized BigInteger getG() { + if (dsa_g != null) return dsa_g; + DSAKey key = getDsaKey(); - if (key != null) { - return key.getParams().getG(); - } - return dsa_g; + if (key != null) return key.getParams().getG(); + return null; } @JRubyMethod(name = "g") @@ -470,6 +535,23 @@ public synchronized IRubyObject set_g(IRubyObject g) { return setKeySpecComponent(SPEC_G, g); } + @JRubyMethod + public IRubyObject set_pqg(IRubyObject p, IRubyObject q, IRubyObject g) { + this.dsa_p = BN.getBigInteger(p); + this.dsa_q = BN.getBigInteger(q); + this.dsa_g = BN.getBigInteger(g); + generateKeyInternal(); + return this; + } + + @JRubyMethod + public IRubyObject set_key(final ThreadContext context, IRubyObject pub_key, IRubyObject priv_key) { + this.dsa_y = BN.getBigInteger(pub_key); + this.dsa_x = BN.getBigInteger(priv_key); + generateKeyInternal(); + return this; + } + @JRubyMethod(name = "priv_key") public synchronized IRubyObject get_priv_key() { DSAPrivateKey key; @@ -500,7 +582,6 @@ public synchronized IRubyObject set_pub_key(IRubyObject pub_key) { private IRubyObject setKeySpecComponent(final int index, final IRubyObject value) { final BigInteger val = BN.getBigInteger(value); - switch (index) { case SPEC_X: this.dsa_x = val; break; case SPEC_Y: this.dsa_y = val; break; @@ -509,19 +590,49 @@ private IRubyObject setKeySpecComponent(final int index, final IRubyObject value case SPEC_G: this.dsa_g = val; break; } + generateKeyInternal(); + return value; + } + + private BigInteger getX() { + if (dsa_x != null) return dsa_x; + + DSAPrivateKey key; + if ((key = this.privateKey) != null) { + return key.getX(); + } + return null; + } + + private BigInteger getY() { + if (dsa_y != null) return dsa_y; + + DSAPublicKey key; + if ((key = this.publicKey) != null) { + return key.getY(); + } + return null; + } + + private void generateKeyInternal() { // Don't access the dsa_p, dsa_q and dsa_g fields directly. They may // have already been consumed and cleared. - BigInteger _dsa_p = getP(); - BigInteger _dsa_q = getQ(); - BigInteger _dsa_g = getG(); + final BigInteger dsa_p = getP(); + final BigInteger dsa_q = getQ(); + final BigInteger dsa_g = getG(); - if ( dsa_x != null && _dsa_p != null && _dsa_q != null && _dsa_g != null ) { + final BigInteger dsa_x = getX(); + final BigInteger dsa_y = getY(); + + if ( dsa_x != null && dsa_p != null && dsa_q != null && dsa_g != null ) { // we now have all private key components. create the key : - DSAPrivateKeySpec spec = new DSAPrivateKeySpec(dsa_x, _dsa_p, _dsa_q, _dsa_g); + DSAPrivateKeySpec spec = new DSAPrivateKeySpec(dsa_x, dsa_p, dsa_q, dsa_g); try { this.privateKey = (DSAPrivateKey) SecurityHelper.getKeyFactory("DSA").generatePrivate(spec); } catch (InvalidKeySpecException e) { + e.printStackTrace(); + throw newDSAError(getRuntime(), "invalid keyspec", e); } catch (NoSuchAlgorithmException e) { @@ -531,9 +642,9 @@ private IRubyObject setKeySpecComponent(final int index, final IRubyObject value this.dsa_x = this.dsa_p = this.dsa_q = this.dsa_g = null; } - if ( dsa_y != null && _dsa_p != null && _dsa_q != null && _dsa_g != null ) { + if ( dsa_y != null && dsa_p != null && dsa_q != null && dsa_g != null ) { // we now have all public key components. create the key : - DSAPublicKeySpec spec = new DSAPublicKeySpec(dsa_y, _dsa_p, _dsa_q, _dsa_g); + DSAPublicKeySpec spec = new DSAPublicKeySpec(dsa_y, dsa_p, dsa_q, dsa_g); try { this.publicKey = (DSAPublicKey) SecurityHelper.getKeyFactory("DSA").generatePublic(spec); } @@ -546,8 +657,6 @@ private IRubyObject setKeySpecComponent(final int index, final IRubyObject value // clear out the specValues this.dsa_y = this.dsa_p = this.dsa_q = this.dsa_g = null; } - - return value; } private static final int SPEC_X = 0; diff --git a/src/main/java/org/jruby/ext/openssl/PKeyEC.java b/src/main/java/org/jruby/ext/openssl/PKeyEC.java index d0369cb9..e698e425 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyEC.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyEC.java @@ -11,6 +11,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; +import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -20,7 +21,6 @@ import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; @@ -30,35 +30,60 @@ import java.security.spec.ECPublicKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.Locale; +import java.util.Optional; import javax.crypto.KeyAgreement; + import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OutputStream; -import org.bouncycastle.asn1.DLSequence; - -import org.bouncycastle.crypto.params.ECNamedDomainParameters; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X962Parameters; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9ECPoint; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; +import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.jcajce.provider.config.ProviderConfiguration; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.ECPointUtil; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec; - +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; import org.jruby.Ruby; import org.jruby.RubyArray; +import org.jruby.RubyBignum; import org.jruby.RubyBoolean; import org.jruby.RubyClass; +import org.jruby.RubyFixnum; import org.jruby.RubyModule; import org.jruby.RubyObject; import org.jruby.RubyString; +import org.jruby.RubySymbol; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Arity; +import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; @@ -66,14 +91,15 @@ import org.jruby.runtime.component.VariableEntry; import org.jruby.ext.openssl.impl.CipherSpec; -import static org.jruby.ext.openssl.OpenSSL.debug; -import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; -import static org.jruby.ext.openssl.PKey._PKey; import org.jruby.ext.openssl.impl.ECPrivateKeyWithName; -import static org.jruby.ext.openssl.impl.PKey.readECPrivateKey; + import org.jruby.ext.openssl.util.ByteArrayOutputStream; import org.jruby.ext.openssl.x509store.PEMInputOutput; +import static org.jruby.ext.openssl.OpenSSL.debug; +import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; +import static org.jruby.ext.openssl.impl.PKey.readECPrivateKey; + /** * OpenSSL::PKey::EC implementation. * @@ -104,10 +130,14 @@ static RubyClass _EC(final Ruby runtime) { return _PKey(runtime).getClass("EC"); } - public static RaiseException newECError(Ruby runtime, String message) { + private static RaiseException newECError(Ruby runtime, String message) { return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message); } + private static RaiseException newECError(Ruby runtime, String message, Exception cause) { + return Utils.newError(runtime, _PKey(runtime).getClass("ECError"), message, cause); + } + @JRubyMethod(meta = true) public static RubyArray builtin_curves(ThreadContext context, IRubyObject self) { final Ruby runtime = context.runtime; @@ -152,37 +182,23 @@ public static RubyArray builtin_curves(ThreadContext context, IRubyObject self) return curves; } - private static ASN1ObjectIdentifier getCurveOID(final String curveName) { - ASN1ObjectIdentifier id; - id = org.bouncycastle.asn1.sec.SECNamedCurves.getOID(curveName); - if ( id != null ) return id; - id = org.bouncycastle.asn1.x9.X962NamedCurves.getOID(curveName); - if ( id != null ) return id; - id = org.bouncycastle.asn1.nist.NISTNamedCurves.getOID(curveName); - if ( id != null ) return id; - id = org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves.getOID(curveName); - if ( id != null ) return id; - throw new IllegalStateException("could not identify curve name: " + curveName); + private static Optional getCurveOID(String curveName) { + if (curveName == null) return Optional.empty(); + // work-around getNamedCurveOid not being able to handle "... " (assuming spacePos + 1 is valid index) + if (curveName.indexOf(' ') == curveName.length() - 1) return Optional.empty(); + return Optional.ofNullable(ECUtil.getNamedCurveOid(curveName)); } private static boolean isCurveName(final String curveName) { - try { - return getCurveOID(curveName) != null; - } - catch (IllegalStateException ex) { return false; } + return getCurveOID(curveName).isPresent(); } private static String getCurveName(final ASN1ObjectIdentifier oid) { - String name; - name = org.bouncycastle.asn1.sec.SECNamedCurves.getName(oid); - if ( name != null ) return name; - name = org.bouncycastle.asn1.x9.X962NamedCurves.getName(oid); - if ( name != null ) return name; - name = org.bouncycastle.asn1.nist.NISTNamedCurves.getName(oid); - if ( name != null ) return name; - name = org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves.getName(oid); - if ( name != null ) return name; - throw new IllegalStateException("could not identify curve name from: " + oid); + final String name = ECUtil.getCurveName(oid); + if (name == null) { + throw new IllegalStateException("could not identify curve name from: " + oid); + } + return name; } public PKeyEC(Ruby runtime, RubyClass type) { @@ -195,8 +211,13 @@ public PKeyEC(Ruby runtime, RubyClass type) { PKeyEC(Ruby runtime, RubyClass type, PrivateKey privKey, PublicKey pubKey) { super(runtime, type); - this.privateKey = privKey; this.publicKey = (ECPublicKey) pubKey; + if (privKey instanceof ECPrivateKey) { + setPrivateKey((ECPrivateKey) privKey); + } else { + this.privateKey = privKey; + setCurveNameFromPublicKeyIfNeeded(); + } } private transient Group group; @@ -206,11 +227,16 @@ public PKeyEC(Ruby runtime, RubyClass type) { private String curveName; - private String getCurveName() { return curveName; } + private String getCurveName() { + if (curveName == null && group != null) { + curveName = group.getCurveName(); + } + return curveName; + } -// private ECNamedCurveParameterSpec getParameterSpec() { -// return ECNamedCurveTable.getParameterSpec( getCurveName() ); -// } + private ECNamedCurveParameterSpec getParameterSpec() { + return ECNamedCurveTable.getParameterSpec(getCurveName()); + } @Override public PublicKey getPublicKey() { return publicKey; } @@ -219,10 +245,13 @@ public PKeyEC(Ruby runtime, RubyClass type) { public PrivateKey getPrivateKey() { return privateKey; } @Override - public String getAlgorithm() { return "EC"; } + public String getAlgorithm() { return "ECDSA"; } + + @Override + public String getKeyType() { return "EC"; } @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) - public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { + public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, Block block) { final Ruby runtime = context.runtime; privateKey = null; publicKey = null; @@ -234,18 +263,17 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a IRubyObject arg = args[0]; if ( arg instanceof Group ) { - this.group = (Group) arg; - this.curveName = this.group.getCurveName(); + setGroup((Group) arg); return this; } IRubyObject pass = null; if ( args.length > 1 ) pass = args[1]; - final char[] passwd = password(pass); + final char[] passwd = password(context, pass, block); final RubyString str = readInitArg(context, arg); final String strJava = str.toString(); - if ( isCurveName(strJava) ) { + if (isCurveName(strJava)) { this.curveName = strJava; return this; } @@ -253,17 +281,17 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a Object key = null; final KeyFactory ecdsaFactory; try { - ecdsaFactory = SecurityHelper.getKeyFactory("ECDSA"); + ecdsaFactory = SecurityHelper.getKeyFactory("EC"); } catch (NoSuchAlgorithmException e) { - throw runtime.newRuntimeError("unsupported key algorithm (ECDSA)"); + throw runtime.newRuntimeError("unsupported key algorithm (EC)"); } catch (RuntimeException e) { - throw runtime.newRuntimeError("unsupported key algorithm (ECDSA) " + e); + throw runtime.newRuntimeError("unsupported key algorithm (EC) " + e); } // TODO: ugly NoClassDefFoundError catching for no BC env. How can we remove this? boolean noClassDef = false; - if ( key == null && ! noClassDef ) { // PEM_read_bio_DSAPrivateKey + if ( key == null && ! noClassDef ) { try { key = readPrivateKey(strJava, passwd); } @@ -295,35 +323,16 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a key = readECPrivateKey(ecdsaFactory, str.getBytes()); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } - catch (InvalidKeySpecException e) { debug(runtime, "PKeyEC could not read private key", e); } - catch (IOException e) { debug(runtime, "PKeyEC could not read private key", e); } + catch (InvalidKeySpecException|IOException e) { debug(runtime, "PKeyEC could not read private key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyEC could not read private key", e); else debugStackTrace(runtime, e); } } -// if ( key == null && ! noClassDef ) { -// try { // readECParameters -// ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(str.getBytes()); -// ECNamedCurveParameterSpec paramSpec = ECNamedCurveTable.getParameterSpec(oid.getId()); -// -// // ecdsaFactory.generatePublic(keySpec) -// -// } -// catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } -// catch (InvalidKeySpecException e) { debug(runtime, "PKeyEC could not read public key", e); } -// catch (IOException e) { debug(runtime, "PKeyEC could not read public key", e); } -// catch (RuntimeException e) { -// if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyEC could not read public key", e); -// else debugStackTrace(runtime, e); -// } -// } if ( key == null ) key = tryPKCS8EncodedKey(runtime, ecdsaFactory, str.getBytes()); if ( key == null ) key = tryX509EncodedKey(runtime, ecdsaFactory, str.getBytes()); - if ( key == null ) throw newECError(runtime, "Neither PUB key nor PRIV key:"); - if ( key instanceof KeyPair ) { final PublicKey pubKey = ((KeyPair) key).getPublic(); final PrivateKey privKey = ((KeyPair) key).getPrivate(); @@ -334,28 +343,39 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a throw newECError(runtime, "Neither PUB key nor PRIV key: (invalid key type " + privKey.getClass().getName() + ")"); } this.publicKey = (ECPublicKey) pubKey; - this.privateKey = (ECPrivateKey) privKey; - unwrapPrivateKeyWithName(); + setPrivateKey((ECPrivateKey) privKey); } else if ( key instanceof ECPrivateKey ) { - this.privateKey = (ECPrivateKey) key; - unwrapPrivateKeyWithName(); + setPrivateKey((ECPrivateKey) key); } else if ( key instanceof ECPublicKey ) { - this.publicKey = (ECPublicKey) key; this.privateKey = null; + this.publicKey = (ECPublicKey) key; + this.privateKey = null; } else { - throw newECError(runtime, "Neither PUB key nor PRIV key: " + key.getClass().getName()); + throw newECError(runtime, "Neither PUB key nor PRIV key: "); } - if ( publicKey != null ) { - publicKey.getParams().getCurve(); - } - // TODO set curveName ?!?!?!?!?!?!?! + setCurveNameFromPublicKeyIfNeeded(); return this; } + private void setCurveNameFromPublicKeyIfNeeded() { + if (curveName == null && publicKey != null) { + final String oid = getCurveNameObjectIdFromKey(getRuntime(), publicKey); + final Optional curveId = getCurveOID(oid); + if (curveId.isPresent()) { + this.curveName = getCurveName(curveId.get()); + } + } + } + + void setPrivateKey(final ECPrivateKey key) { + this.privateKey = key; + unwrapPrivateKeyWithName(); + } + private void unwrapPrivateKeyWithName() { final ECPrivateKey privKey = (ECPrivateKey) this.privateKey; if ( privKey instanceof ECPrivateKeyWithName ) { @@ -364,6 +384,40 @@ private void unwrapPrivateKeyWithName() { } } + private static String getCurveNameObjectIdFromKey(final Ruby runtime, final ECPublicKey key) { + try { + AlgorithmParameters algParams = AlgorithmParameters.getInstance("EC"); + algParams.init(key.getParams()); + return algParams.getParameterSpec(ECGenParameterSpec.class).getName(); + } + catch (NoSuchAlgorithmException|InvalidParameterSpecException ex) { + throw newECError(runtime, ex.getMessage()); + } + catch (Exception ex) { + throw (RaiseException) newECError(runtime, ex.toString()).initCause(ex); + } + } + + private void setGroup(final Group group) { + this.group = group; + this.curveName = this.group.getCurveName(); + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + @Override + public IRubyObject initialize_copy(final IRubyObject original) { + if (this == original) return this; + checkFrozen(); + + final PKeyEC that = (PKeyEC) original; + this.publicKey = that.publicKey; + this.privateKey = that.privateKey; + this.curveName = that.curveName; + this.group = that.group; + + return this; + } + //private static ECNamedCurveParameterSpec readECParameters(final byte[] input) throws IOException { // ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(input); // return ECNamedCurveTable.getParameterSpec(oid.getId()); @@ -374,13 +428,12 @@ public IRubyObject check_key(final ThreadContext context) { return context.runtime.getTrue(); // TODO not implemented stub } - @JRubyMethod(name = "generate_key") + @JRubyMethod(name = "generate_key!", alias = "generate_key") public PKeyEC generate_key(final ThreadContext context) { - // final ECDomainParameters params = getDomainParameters(); try { ECGenParameterSpec genSpec = new ECGenParameterSpec(getCurveName()); - KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("ECDSA"); // "BC" - gen.initialize(genSpec, new SecureRandom()); + KeyPairGenerator gen = SecurityHelper.getKeyPairGenerator("EC"); // "BC" + gen.initialize(genSpec, OpenSSL.getSecureRandom(context)); KeyPair pair = gen.generateKeyPair(); this.publicKey = (ECPublicKey) pair.getPublic(); this.privateKey = pair.getPrivate(); @@ -391,56 +444,84 @@ public PKeyEC generate_key(final ThreadContext context) { return this; } + @JRubyMethod(meta = true) + public static IRubyObject generate(final ThreadContext context, final IRubyObject self, final IRubyObject group) { + PKeyEC randomKey = new PKeyEC(context.runtime, (RubyClass) self); + + if (group instanceof Group) { + randomKey.setGroup((Group) group); + } else { + randomKey.curveName = group.convertToString().toString(); + } + + return randomKey.generate_key(context); + } + @JRubyMethod(name = "dsa_sign_asn1") public IRubyObject dsa_sign_asn1(final ThreadContext context, final IRubyObject data) { + if (privateKey == null) { + throw newECError(context.runtime, "Private EC key needed!"); + } try { - ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec(getCurveName()); - ASN1ObjectIdentifier oid = getCurveOID(getCurveName()); - ECNamedDomainParameters domainParams = new ECNamedDomainParameters(oid, - params.getCurve(), params.getG(), params.getN(), params.getH(), params.getSeed() - ); + final ECNamedCurveParameterSpec params = getParameterSpec(); final ECDSASigner signer = new ECDSASigner(); - final ECPrivateKey privKey = (ECPrivateKey) this.privateKey; - signer.init(true, new ECPrivateKeyParameters(privKey.getS(), domainParams)); - - final byte[] message = data.convertToString().getBytes(); - BigInteger[] signature = signer.generateSignature(message); // [r, s] - -// final byte[] r = signature[0].toByteArray(); -// final byte[] s = signature[1].toByteArray(); -// // ASN.1 encode as: 0x30 len 0x02 rlen (r) 0x02 slen (s) -// final int len = 1 + (1 + r.length) + 1 + (1 + s.length); -// -// final byte[] encoded = new byte[1 + 1 + len]; int i; -// encoded[0] = 0x30; -// encoded[1] = (byte) len; -// encoded[2] = 0x20; -// encoded[3] = (byte) r.length; -// System.arraycopy(r, 0, encoded, i = 4, r.length); i += r.length; -// encoded[i++] = 0x20; -// encoded[i++] = (byte) s.length; -// System.arraycopy(s, 0, encoded, i, s.length); + signer.init(true, new ECPrivateKeyParameters( + ((ECPrivateKey) this.privateKey).getS(), + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()) + )); + + BigInteger[] signature = signer.generateSignature(data.convertToString().getBytes()); // [r, s] ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ASN1OutputStream asn1 = new ASN1OutputStream(bytes); + ASN1OutputStream asn1 = ASN1OutputStream.create(bytes, ASN1Encoding.DER); - ASN1EncodableVector v = new ASN1EncodableVector(); + ASN1EncodableVector v = new ASN1EncodableVector(2); v.add(new ASN1Integer(signature[0])); // r v.add(new ASN1Integer(signature[1])); // s - asn1.writeObject(new DLSequence(v)); + asn1.writeObject(new DERSequence(v)); + asn1.close(); return StringHelper.newString(context.runtime, bytes.buffer(), bytes.size()); } catch (IOException ex) { - throw newECError(context.runtime, ex.toString()); + throw newECError(context.runtime, ex.getMessage()); } - catch (RuntimeException ex) { - throw newECError(context.runtime, ex.toString()); + catch (Exception ex) { + throw newECError(context.runtime, ex.toString(), ex); } } + @JRubyMethod(name = "dsa_verify_asn1") + public IRubyObject dsa_verify_asn1(final ThreadContext context, final IRubyObject data, final IRubyObject sign) { + final Ruby runtime = context.runtime; + try { + final ECNamedCurveParameterSpec params = getParameterSpec(); + + final ECDSASigner signer = new ECDSASigner(); + signer.init(false, new ECPublicKeyParameters( + EC5Util.convertPoint(publicKey.getParams(), publicKey.getW()), + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()) + )); + + ASN1Primitive vec = new ASN1InputStream(sign.convertToString().getBytes()).readObject(); + + if (!(vec instanceof ASN1Sequence)) { + throw newECError(runtime, "invalid signature (not a sequence)"); + } + + ASN1Sequence seq = (ASN1Sequence) vec; + ASN1Integer r = ASN1Integer.getInstance(seq.getObjectAt(0)); + ASN1Integer s = ASN1Integer.getInstance(seq.getObjectAt(1)); + + boolean verify = signer.verifySignature(data.convertToString().getBytes(), r.getPositiveValue(), s.getPositiveValue()); + return runtime.newBoolean(verify); + } + catch (IOException|IllegalArgumentException|IllegalStateException ex) { + throw newECError(runtime, "invalid signature: " + ex.getMessage(), ex); + } + } @JRubyMethod(name = "dh_compute_key") public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject point) { @@ -462,23 +543,22 @@ public IRubyObject dh_compute_key(final ThreadContext context, final IRubyObject final byte[] secret = agreement.generateSecret(); return StringHelper.newString(context.runtime, secret); } - catch (NoSuchAlgorithmException ex) { - throw newECError(context.runtime, ex.toString()); - } catch (InvalidKeyException ex) { - throw newECError(context.runtime, ex.toString()); + throw newECError(context.runtime, "invalid key: " + ex.getMessage()); } catch (GeneralSecurityException ex) { throw newECError(context.runtime, ex.toString()); } } + @JRubyMethod + public IRubyObject oid() { + return getRuntime().newString("id-ecPublicKey"); + } + private Group getGroup(boolean required) { if (group == null) { - if (publicKey != null) { - return group = new Group(getRuntime(), this); - } - if (required) throw new IllegalStateException("no group (without public key)"); + return group = new Group(getRuntime(), this); } return group; } @@ -516,7 +596,7 @@ public IRubyObject set_public_key(final ThreadContext context, final IRubyObject final Point point = (Point) arg; ECPublicKeySpec keySpec = new ECPublicKeySpec(point.asECPoint(), getParamSpec()); try { - this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("ECDSA").generatePublic(keySpec); + this.publicKey = (ECPublicKey) SecurityHelper.getKeyFactory("EC").generatePublic(keySpec); return arg; } catch (GeneralSecurityException ex) { @@ -524,9 +604,13 @@ public IRubyObject set_public_key(final ThreadContext context, final IRubyObject } } + /** + * @see ECNamedCurveSpec + */ private static ECParameterSpec getParamSpec(final String curveName) { - ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveName); - return new ECNamedCurveSpec(spec.getName(), spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed()); + final ECNamedCurveParameterSpec ecCurveParamSpec = ECNamedCurveTable.getParameterSpec(curveName); + final EllipticCurve curve = EC5Util.convertCurve(ecCurveParamSpec.getCurve(), ecCurveParamSpec.getSeed()); + return EC5Util.convertSpec(curve, ecCurveParamSpec); } private ECParameterSpec getParamSpec() { @@ -554,7 +638,7 @@ public IRubyObject set_private_key(final ThreadContext context, final IRubyObjec } ECPrivateKeySpec keySpec = new ECPrivateKeySpec(s, getParamSpec()); try { - this.privateKey = SecurityHelper.getKeyFactory("ECDSA").generatePrivate(keySpec); + this.privateKey = SecurityHelper.getKeyFactory("EC").generatePrivate(keySpec); return arg; } catch (GeneralSecurityException ex) { @@ -562,74 +646,194 @@ public IRubyObject set_private_key(final ThreadContext context, final IRubyObjec } } - @JRubyMethod(name = "public_key?") + @JRubyMethod(name = "public?", alias = "public_key?") public RubyBoolean public_p() { return publicKey != null ? getRuntime().getTrue() : getRuntime().getFalse(); } - @JRubyMethod(name = "private_key?") + @JRubyMethod(name = "private?", alias = "private_key?") public RubyBoolean private_p() { return privateKey != null ? getRuntime().getTrue() : getRuntime().getFalse(); } + @JRubyMethod + public RubyString public_to_der(ThreadContext context) { + return public_to_der(context.runtime); + } + + private RubyString public_to_der(final Ruby runtime) { + final byte[] bytes; + try { + bytes = publicKey.getEncoded(); + } catch (Exception e) { + throw newECError(runtime, e.getMessage(), e); + } + return StringHelper.newString(runtime, bytes); + } + @Override @JRubyMethod(name = "to_der") public RubyString to_der() { - final byte[] bytes; + final Ruby runtime = getRuntime(); + if (publicKey != null && privateKey == null) { + return public_to_der(runtime); + } + if (privateKey == null) { + throw new IllegalStateException("private key as well as public key are null"); + } + try { - bytes = toDER(); + byte[] encoded = toPrivateKeyStructure((ECPrivateKey) privateKey, publicKey, false).getEncoded(ASN1Encoding.DER); + return StringHelper.newString(runtime, encoded); + } catch (Exception e) { + throw newECError(runtime, e.getMessage(), e); + } + } + + @JRubyMethod + public RubyString private_to_der(ThreadContext context) { + return private_to_der(context.runtime); + } + + private RubyString private_to_der(final Ruby runtime) { + final byte[] encoded; + if (privateKey instanceof ECPrivateKey) { + try { + encoded = toPrivateKeyInfo((ECPrivateKey) privateKey, publicKey).getEncoded(ASN1Encoding.DER); + } catch (IOException e) { + throw newECError(runtime, e.getMessage(), e); + } + } else { + try { + encoded = privateKey.getEncoded(); + } catch (Exception e) { + throw newECError(runtime, e.getMessage(), e); + } } - catch (IOException e) { - throw newECError(getRuntime(), e.getMessage()); + return StringHelper.newString(runtime, encoded); + } + + private static org.bouncycastle.asn1.sec.ECPrivateKey toPrivateKeyStructure(final ECPrivateKey privateKey, + final ECPublicKey publicKey, + final boolean compressed) throws IOException { + final ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION; + final ECParameterSpec ecSpec = privateKey.getParams(); + final X962Parameters params = getDomainParametersFromName(ecSpec, compressed); + + int orderBitLength = ECUtil.getOrderBitLength(configuration, ecSpec == null ? null : ecSpec.getOrder(), privateKey.getS()); + + if (publicKey == null) { + return new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, privateKey.getS(), params); } - return StringHelper.newString(getRuntime(), bytes); + + SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(publicKey.getEncoded())); + return new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, privateKey.getS(), info.getPublicKeyData(), params); } - private byte[] toDER() throws IOException { - if ( publicKey != null && privateKey == null ) { - return publicKey.getEncoded(); + private static PrivateKeyInfo toPrivateKeyInfo(final ECPrivateKey privateKey, + final ECPublicKey publicKey) throws IOException { + final ECParameterSpec ecSpec = privateKey.getParams(); + final X962Parameters params = getDomainParametersFromName(ecSpec, false); + + org.bouncycastle.asn1.sec.ECPrivateKey keyStructure = toPrivateKeyStructure(privateKey, publicKey, false); + return new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure); + } + + private static X962Parameters getDomainParametersFromName(ECParameterSpec ecSpec, boolean compressed) { + if (ecSpec instanceof ECNamedCurveSpec) { + ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec)ecSpec).getName()); + if (curveOid == null) + { + curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec)ecSpec).getName()); + } + return new X962Parameters(curveOid); } - if ( privateKey == null ) { - throw new IllegalStateException("private key as well as public key are null"); + if (ecSpec == null) { + return new X962Parameters(DERNull.INSTANCE); } - return privateKey.getEncoded(); + ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve()); + + X9ECParameters ecParameters = new X9ECParameters( + curve, + new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), compressed), + ecSpec.getOrder(), + BigInteger.valueOf(ecSpec.getCofactor()), + ecSpec.getCurve().getSeed()); + + return new X962Parameters(ecParameters); } @Override @JRubyMethod(name = "to_pem", alias = "export", rest = true) - public RubyString to_pem(final IRubyObject[] args) { - Arity.checkArgumentCount(getRuntime(), args, 0, 2); + public RubyString to_pem(ThreadContext context, final IRubyObject[] args) { + Arity.checkArgumentCount(context.runtime, args, 0, 2); CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); - if ( args.length > 1 ) passwd = password( args[1] ); + if ( args.length > 1 ) passwd = password(context, args[1], null); + } + + if (privateKey == null) { + return public_to_pem(context); } try { final StringWriter writer = new StringWriter(); - if ( privateKey != null ) { - PEMInputOutput.writeECPrivateKey(writer, (ECPrivateKey) privateKey, spec, passwd); - } - else { - PEMInputOutput.writeECPublicKey(writer, publicKey); - } - return RubyString.newString(getRuntime(), writer.getBuffer()); + PEMInputOutput.writeECPrivateKey(writer, (ECPrivateKey) privateKey, spec, passwd); + return RubyString.newString(context.runtime, writer.getBuffer()); + } catch (IOException ex) { + throw newECError(context.runtime, ex.getMessage()); } - catch (IOException ex) { - throw newECError(getRuntime(), ex.getMessage()); + } + + @JRubyMethod + public RubyString public_to_pem(ThreadContext context) { + try { + final StringWriter writer = new StringWriter(); + PEMInputOutput.writeECPublicKey(writer, publicKey); + return RubyString.newString(context.runtime, writer.getBuffer()); + } catch (IOException ex) { + throw newECError(context.runtime, ex.getMessage()); + } + } + + @JRubyMethod + public RubyString to_text() { + StringBuilder result = new StringBuilder(); + final ECParameterSpec spec = getParamSpec(); + result.append("Private-Key: (").append(spec.getOrder().bitLength()).append(" bit)").append('\n'); + + if (privateKey instanceof ECPrivateKey) { + result.append("priv:"); + addSplittedAndFormatted(result, ((ECPrivateKey) privateKey).getS()); + } + + if (publicKey != null) { + result.append("pub:"); + final byte[] pubBytes = encodeCompressed(publicKey.getW()); + final StringBuilder hexBytes = new StringBuilder(pubBytes.length * 2); + for (byte b: pubBytes) hexBytes.append(Integer.toHexString(Byte.toUnsignedInt(b))); + addSplittedAndFormatted(result, hexBytes); + } + result.append("ASN1 OID: ").append(getCurveName()).append('\n'); + + return RubyString.newString(getRuntime(), result); + } + + private enum PointConversion { + COMPRESSED, UNCOMPRESSED, HYBRID; + + String toRubyString() { + return super.toString().toLowerCase(Locale.ROOT); } } @JRubyClass(name = "OpenSSL::PKey::EC::Group") public static final class Group extends RubyObject { - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public Group allocate(Ruby runtime, RubyClass klass) { return new Group(runtime, klass); } - }; - static void createGroup(final Ruby runtime, final RubyClass EC, final RubyClass OpenSSLError) { - RubyClass Group = EC.defineClassUnder("Group", runtime.getObject(), ALLOCATOR); + RubyClass Group = EC.defineClassUnder("Group", runtime.getObject(), Group::new); // OpenSSL::PKey::EC::Group::Error Group.defineClassUnder("Error", OpenSSLError, OpenSSLError.getAllocator()); @@ -637,9 +841,12 @@ static void createGroup(final Ruby runtime, final RubyClass EC, final RubyClass Group.defineAnnotatedMethods(Group.class); } - private transient PKeyEC key; - private ECParameterSpec paramSpec; - private RubyString curve_name; + private transient ECParameterSpec paramSpec; + + private PointConversion conversionForm = PointConversion.UNCOMPRESSED; + + private String curveName; + private RubyString impl_curve_name; public Group(Ruby runtime, RubyClass type) { super(runtime, type); @@ -647,13 +854,7 @@ public Group(Ruby runtime, RubyClass type) { Group(Ruby runtime, PKeyEC key) { this(runtime, _EC(runtime).getClass("Group")); - this.key = key; - this.paramSpec = key.publicKey.getParams(); - } - - private String getCurveName() { - if (key != null) return key.getCurveName(); - return curve_name.toString(); + setCurveName(runtime, key.getCurveName()); } @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) @@ -664,74 +865,82 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a IRubyObject arg = args[0]; if ( arg instanceof Group ) { - IRubyObject curve_name = ((Group) arg).curve_name(context); - this.curve_name = curve_name.isNil() ? null : (RubyString) curve_name; + this.curveName = ((Group) arg).curveName; return this; } - this.curve_name = ((RubyString) arg); - - // TODO PEM/DER parsing not implemented + this.impl_curve_name = arg.convertToString(); } return this; } + private String getCurveName() { + if (curveName == null) { + assert impl_curve_name != null; + return impl_curve_name.asJavaString(); + } + return curveName; + } + @Override @JRubyMethod(name = { "==", "eql?" }) public IRubyObject op_equal(final ThreadContext context, final IRubyObject obj) { - if ( paramSpec == null ) return context.nil; + final Ruby runtime = context.runtime; if ( obj instanceof Group ) { final Group that = (Group) obj; - boolean equals = this.paramSpec.equals(that.paramSpec); - return context.runtime.newBoolean(equals); + return context.runtime.newBoolean(this.implCurveName(runtime).equals(that.implCurveName(runtime))); } return context.runtime.getFalse(); } @JRubyMethod public IRubyObject curve_name(final ThreadContext context) { - if (curve_name == null) { - String prefix, curveName = key.getCurveName(); - // BC 1.54: "brainpoolP512t1" 1.55: "brainpoolp512t1" - if (curveName.startsWith(prefix = "brainpoolp")) { - curveName = "brainpoolP" + curveName.substring(prefix.length()); - } - curve_name = RubyString.newString(context.runtime, curveName); + return implCurveName(context.runtime).dup(); + } + + private RubyString implCurveName(final Ruby runtime) { + if (impl_curve_name == null && curveName != null) { + setCurveName(runtime, curveName); + } + return impl_curve_name; + } + + private void setCurveName(final Ruby runtime, String curveName) { + assert curveName != null; + this.curveName = curveName; + String prefix; + // BC 1.54: "brainpoolP512t1" 1.55: "brainpoolp512t1" + if (curveName.startsWith(prefix = "brainpoolp")) { + curveName = "brainpoolP" + curveName.substring(prefix.length()); } - return curve_name.dup(); + impl_curve_name = RubyString.newString(runtime, curveName); } @JRubyMethod public IRubyObject order(final ThreadContext context) { - if ( paramSpec == null ) return context.nil; - return BN.newBN(context.runtime, paramSpec.getOrder()); + return BN.newBN(context.runtime, getParamSpec().getOrder()); } @JRubyMethod public IRubyObject cofactor(final ThreadContext context) { - if ( paramSpec == null ) return context.nil; - return context.runtime.newFixnum(paramSpec.getCofactor()); + return context.runtime.newFixnum(getParamSpec().getCofactor()); } @JRubyMethod public IRubyObject seed(final ThreadContext context) { - if ( paramSpec == null ) return context.nil; - final byte[] seed = paramSpec.getCurve().getSeed(); + final byte[] seed = getCurve().getSeed(); return seed == null ? context.nil : StringHelper.newString(context.runtime, seed); } @JRubyMethod public IRubyObject degree(final ThreadContext context) { - if ( paramSpec == null ) return context.nil; - final int fieldSize = paramSpec.getCurve().getField().getFieldSize(); + final int fieldSize = getCurve().getField().getFieldSize(); return context.runtime.newFixnum(fieldSize); } @JRubyMethod public IRubyObject generator(final ThreadContext context) { - if ( paramSpec == null ) return context.nil; - final ECPoint generator = paramSpec.getGenerator(); - //final int bitLength = paramSpec.getOrder().bitLength(); + final ECPoint generator = getParamSpec().getGenerator(); return new Point(context.runtime, generator, this); } @@ -742,12 +951,14 @@ public RubyString to_pem(final ThreadContext context, final IRubyObject[] args) CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); - if ( args.length > 1 ) passwd = password(args[1]); + if ( args.length > 1 ) passwd = password(context, args[1], null); } try { final StringWriter writer = new StringWriter(); - PEMInputOutput.writeECParameters(writer, getCurveOID(getCurveName()), spec, passwd); + final ASN1ObjectIdentifier oid = getCurveOID(getCurveName()) + .orElseThrow(() -> newECError(context.runtime, "invalid curve name: " + getCurveName())); + PEMInputOutput.writeECParameters(writer, oid, spec, passwd); return RubyString.newString(context.runtime, writer.getBuffer()); } catch (IOException ex) { @@ -755,13 +966,43 @@ public RubyString to_pem(final ThreadContext context, final IRubyObject[] args) } } - final EllipticCurve getCurve() { + private ECParameterSpec getParamSpec() { if (paramSpec == null) { - paramSpec = getParamSpec(getCurveName()); + paramSpec = PKeyEC.getParamSpec(getCurveName()); + } + return paramSpec; + } + + EllipticCurve getCurve() { + return getParamSpec().getCurve(); + } + + int getBitLength() { + return getParamSpec().getOrder().bitLength(); + } + + @JRubyMethod + public RubySymbol point_conversion_form(final ThreadContext context) { + return context.runtime.newSymbol(this.conversionForm.toRubyString()); + } + + @JRubyMethod(name = "point_conversion_form=") + public IRubyObject set_point_conversion_form(final ThreadContext context, final IRubyObject form) { + this.conversionForm = parse_point_conversion_form(context.runtime, form); + return form; + } + + static PointConversion parse_point_conversion_form(final Ruby runtime, final IRubyObject form) { + if (form instanceof RubySymbol) { + final String pointConversionForm = ((RubySymbol) form).asJavaString(); + if ("uncompressed".equals(pointConversionForm)) return PointConversion.UNCOMPRESSED; + if ("compressed".equals(pointConversionForm)) return PointConversion.COMPRESSED; + if ("hybrid".equals(pointConversionForm)) return PointConversion.HYBRID; } - return paramSpec.getCurve(); + throw runtime.newArgumentError("unsupported point conversion form: " + form.inspect()); } + // @Override // @JRubyMethod // @SuppressWarnings("unchecked") @@ -781,12 +1022,8 @@ final EllipticCurve getCurve() { @JRubyClass(name = "OpenSSL::PKey::EC::Point") public static final class Point extends RubyObject { - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public Point allocate(Ruby runtime, RubyClass klass) { return new Point(runtime, klass); } - }; - static void createPoint(final Ruby runtime, final RubyClass EC, final RubyClass OpenSSLError) { - RubyClass Point = EC.defineClassUnder("Point", runtime.getObject(), ALLOCATOR); + RubyClass Point = EC.defineClassUnder("Point", runtime.getObject(), Point::new); // OpenSSL::PKey::EC::Point::Error Point.defineClassUnder("Error", OpenSSLError, OpenSSLError.getAllocator()); @@ -798,14 +1035,11 @@ public Point(Ruby runtime, RubyClass type) { super(runtime, type); } - // private transient ECPublicKey publicKey; private ECPoint point; - //private int bitLength; private Group group; Point(Ruby runtime, ECPublicKey publicKey, Group group) { this(runtime, _EC(runtime).getClass("Point")); - //this.publicKey = publicKey; this.point = publicKey.getW(); this.group = group; } @@ -821,36 +1055,54 @@ private static RaiseException newError(final Ruby runtime, final String message) return Utils.newError(runtime, Error, message); } - @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) - public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { - final Ruby runtime = context.runtime; + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(final ThreadContext context, final IRubyObject groupOrPoint) { + getPointAndGroup(context, groupOrPoint); - final int argc = Arity.checkArgumentCount(runtime, args, 1, 2); - final IRubyObject arg = args[0]; + return this; + } - if ( arg instanceof Point ) { - this.group = ((Point) arg).group; - this.point = ((Point) arg).point; + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(final ThreadContext context, final IRubyObject groupOrPoint, final IRubyObject bn) { + if (getPointAndGroup(context, groupOrPoint)) { return this; } - if ( arg instanceof Group ) { - this.group = (Group) arg; + final byte[] encoded; + if (bn instanceof BN) { + encoded = ((BN) bn).getValue().abs().toByteArray(); + } else { + encoded = bn.convertToString().getBytes(); } - if ( argc == 2 ) { // (group, bn) - final byte[] encoded = ((BN) args[1]).getValue().abs().toByteArray(); - try { - this.point = ECPointUtil.decodePoint(group.getCurve(), encoded); - } - catch (IllegalArgumentException ex) { - // MRI: OpenSSL::PKey::EC::Point::Error: invalid encoding - throw newError(context.runtime, ex.getMessage()); - } + try { + this.point = ECPointUtil.decodePoint(group.getCurve(), encoded); + } + catch (IllegalArgumentException ex) { + // MRI: OpenSSL::PKey::EC::Point::Error: invalid encoding + throw newError(context.runtime, ex.getMessage()); } return this; } + private boolean getPointAndGroup(ThreadContext context, IRubyObject groupOrPoint) { + final Ruby runtime = context.runtime; + + if ( groupOrPoint instanceof Point) { + this.group = ((Point) groupOrPoint).group; + this.point = ((Point) groupOrPoint).point; + return true; + } + + if ( groupOrPoint instanceof Group) { + this.group = (Group) groupOrPoint; + this.point = this.group.getParamSpec().getGenerator(); + } else { + throw runtime.newTypeError(groupOrPoint, _EC(runtime).getClass("Group")); + } + return false; + } + @Override @JRubyMethod(name = { "==", "eql?" }) public IRubyObject op_equal(final ThreadContext context, final IRubyObject obj) { @@ -874,16 +1126,50 @@ private ECPoint asECPoint() { return point; // return publicKey.getW(); } - private int bitLength() { - return group.paramSpec.getOrder().bitLength(); + private PointConversion getPointConversionForm() { + if (group == null) return null; + return group.conversionForm; } @JRubyMethod public BN to_bn(final ThreadContext context) { - final byte[] encoded = encode(bitLength(), point); + return toBN(context, getPointConversionForm()); // group.point_conversion_form + } + + @JRubyMethod + public BN to_bn(final ThreadContext context, final IRubyObject conversion_form) { + return toBN(context, Group.parse_point_conversion_form(context.runtime, conversion_form)); + } + + private BN toBN(final ThreadContext context, final PointConversion conversionForm) { + final byte[] encoded = encodePoint(conversionForm); return BN.newBN(context.runtime, new BigInteger(1, encoded)); } + private byte[] encodePoint(final PointConversion conversionForm) { + final byte[] encoded; + switch (conversionForm) { + case UNCOMPRESSED: + assert group != null; + encoded = encodeUncompressed(group.getBitLength(), point); + break; + case COMPRESSED: + encoded = encodeCompressed(point); + break; + case HYBRID: + throw getRuntime().newNotImplementedError(":hybrid compression not implemented"); + default: + throw new AssertionError("unexpected conversion form: " + conversionForm); + } + return encoded; + } + + @JRubyMethod + public IRubyObject to_octet_string(final ThreadContext context, final IRubyObject conversion_form) { + final PointConversion conversionForm = Group.parse_point_conversion_form(context.runtime, conversion_form); + return StringHelper.newString(context.runtime, encodePoint(conversionForm)); + } + private boolean isInfinity() { return point == ECPoint.POINT_INFINITY; } @@ -907,38 +1193,168 @@ public IRubyObject inspect() { return ObjectSupport.inspect(this, (List) Collections.singletonList(entry)); } + @JRubyMethod(name = "add") + public IRubyObject add(final ThreadContext context, final IRubyObject other) { + Ruby runtime = context.runtime; + + org.bouncycastle.math.ec.ECPoint pointSelf, pointOther, pointResult; + + Group groupV = this.group; + Point result; + + ECCurve selfCurve = EC5Util.convertCurve(groupV.getCurve()); + pointSelf = EC5Util.convertPoint(selfCurve, asECPoint()); + + Point otherPoint = (Point) other; + ECCurve otherCurve = EC5Util.convertCurve(otherPoint.group.getCurve()); + pointOther = EC5Util.convertPoint(otherCurve, otherPoint.asECPoint()); + + pointResult = pointSelf.add(pointOther); + if (pointResult == null) { + newECError(runtime, "EC_POINT_add"); + } + + result = new Point(runtime, EC5Util.convertPoint(pointResult), group); + + return result; + } + + @JRubyMethod(name = "mul") + public IRubyObject mul(final ThreadContext context, final IRubyObject bn1) { + Ruby runtime = context.runtime; + + if (bn1 instanceof RubyArray) { + throw runtime.newNotImplementedError("calling #mul with arrays is not supported by this OpenSSL version"); + } + + org.bouncycastle.math.ec.ECPoint pointSelf; + + Group groupV = this.group; + + ECCurve selfCurve = EC5Util.convertCurve(groupV.getCurve()); + pointSelf = EC5Util.convertPoint(selfCurve, asECPoint()); + + BigInteger bn = getBigInteger(context, bn1); + + org.bouncycastle.math.ec.ECPoint mulPoint = ECAlgorithms.referenceMultiply(pointSelf, bn); + if (mulPoint == null) { + throw newECError(runtime, "bad multiply result"); + } + + return new Point(runtime, EC5Util.convertPoint(mulPoint), groupV); + } + + @JRubyMethod(name = "mul") + public IRubyObject mul(final ThreadContext context, final IRubyObject bn1, final IRubyObject bn2) { + Ruby runtime = context.runtime; + + if (bn1 instanceof RubyArray) { + throw runtime.newNotImplementedError("calling #mul with arrays is not supported by this OpenSSL version"); + } + + org.bouncycastle.math.ec.ECPoint pointSelf, pointResult; + + Group groupV = this.group; + + ECCurve selfCurve = EC5Util.convertCurve(groupV.getCurve()); + pointSelf = EC5Util.convertPoint(selfCurve, asECPoint()); + + ECCurve resultCurve = EC5Util.convertCurve(groupV.getCurve()); + pointResult = EC5Util.convertPoint(resultCurve, ((Point) groupV.generator(context)).asECPoint()); + + BigInteger bn = getBigInteger(context, bn1); + BigInteger bn_g = getBigInteger(context, bn2); + + org.bouncycastle.math.ec.ECPoint mulPoint = ECAlgorithms.sumOfTwoMultiplies(pointResult, bn_g, pointSelf, bn); + + if (mulPoint == null) { + throw newECError(runtime, "bad multiply result"); + } + + return new Point(runtime, EC5Util.convertPoint(mulPoint), groupV); + } + + @JRubyMethod(name = "mul") + public IRubyObject mul(final ThreadContext context, final IRubyObject bns, final IRubyObject points, final IRubyObject bn2) { + throw context.runtime.newNotImplementedError("calling #mul with arrays is not supported by this OpenSSL version"); + } + + @Deprecated + public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { + final int argc = Arity.checkArgumentCount(context.runtime, args, 1, 2); + + switch (argc) { + case 1: + return initialize(context, args[0]); + case 2: + return initialize(context, args[0], args[1]); + default: + throw context.runtime.newArgumentError(args.length, 1); + } + } + + } + + private static BigInteger getBigInteger(ThreadContext context, IRubyObject arg1) { + BigInteger bn; + if (arg1 instanceof RubyFixnum) { + bn = BigInteger.valueOf(arg1.convertToInteger().getLongValue()); + } else if (arg1 instanceof RubyBignum) { + bn = ((RubyBignum) arg1).getValue(); + } else if (arg1 instanceof BN) { + bn = ((BN) arg1).getValue(); + } else { + Ruby runtime = context.runtime; + throw runtime.newTypeError(arg1, runtime.getInteger()); + } + return bn; } static byte[] encode(final ECPublicKey pubKey) { - return encode(pubKey.getParams().getOrder().bitLength(), pubKey.getW()); + return encodeUncompressed(pubKey.getParams().getOrder().bitLength(), pubKey.getW()); } - private static byte[] encode(final int bitLength, final ECPoint point) { - if ( point == ECPoint.POINT_INFINITY ) return new byte[1]; + private static byte[] encodeUncompressed(final int fieldSize, final ECPoint point) { + if (point == ECPoint.POINT_INFINITY) return new byte[1]; - final int bytesLength = (bitLength + 7) / 8; - byte[] encoded = new byte[1 + bytesLength + bytesLength]; + final int expLength = (fieldSize + 7) / 8; + + byte[] encoded = new byte[1 + expLength + expLength]; encoded[0] = 0x04; + addIntBytes(point.getAffineX(), expLength, encoded, 1); + addIntBytes(point.getAffineY(), expLength, encoded, 1 + expLength); + + return encoded; + } + + private static byte[] encodeCompressed(final ECPoint point) { + if (point == ECPoint.POINT_INFINITY) return new byte[1]; + + final int bytesLength = point.getAffineX().bitLength() / 8 + 1; + + byte[] encoded = new byte[1 + bytesLength]; + + encoded[0] = (byte) (point.getAffineY().testBit(0) ? 0x03 : 0x02); + addIntBytes(point.getAffineX(), bytesLength, encoded, 1); - addIntBytes(point.getAffineY(), bytesLength, encoded, 1 + bytesLength); return encoded; } - private static void addIntBytes(BigInteger i, final int length, final byte[] dest, final int destOffset) { - final byte[] bytes = i.toByteArray(); + private static void addIntBytes(final BigInteger value, final int length, final byte[] dest, final int destOffset) { + final byte[] in = value.toByteArray(); - if (length < bytes.length) { - System.arraycopy(bytes, bytes.length - length, dest, destOffset, length); + if (length < in.length) { + System.arraycopy(in, in.length - length, dest, destOffset, length); } - else if (length > bytes.length) { - System.arraycopy(bytes, 0, dest, destOffset + (length - bytes.length), bytes.length); + else if (length > in.length) { + System.arraycopy(in, 0, dest, destOffset + (length - in.length), in.length); } else { - System.arraycopy(bytes, 0, dest, destOffset, length); + System.arraycopy(in, 0, dest, destOffset, length); } } -} \ No newline at end of file +} diff --git a/src/main/java/org/jruby/ext/openssl/PKeyRSA.java b/src/main/java/org/jruby/ext/openssl/PKeyRSA.java index 6e96f7f1..7e0e28bb 100644 --- a/src/main/java/org/jruby/ext/openssl/PKeyRSA.java +++ b/src/main/java/org/jruby/ext/openssl/PKeyRSA.java @@ -40,16 +40,18 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; import static javax.crypto.Cipher.*; +import org.bouncycastle.asn1.ASN1Primitive; import org.jruby.Ruby; import org.jruby.RubyClass; import org.jruby.RubyBignum; @@ -62,6 +64,7 @@ import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Arity; +import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; @@ -70,10 +73,11 @@ import org.jruby.ext.openssl.impl.CipherSpec; import org.jruby.ext.openssl.x509store.PEMInputOutput; import static org.jruby.ext.openssl.OpenSSL.*; -import static org.jruby.ext.openssl.PKey._PKey; import static org.jruby.ext.openssl.impl.PKey.readRSAPrivateKey; import static org.jruby.ext.openssl.impl.PKey.readRSAPublicKey; +import static org.jruby.ext.openssl.impl.PKey.toASN1Primitive; import static org.jruby.ext.openssl.impl.PKey.toDerRSAKey; +import static org.jruby.ext.openssl.impl.PKey.toDerRSAPublicKey; /** * @author Ola Bini @@ -106,7 +110,11 @@ public static RaiseException newRSAError(Ruby runtime, String message) { } static RaiseException newRSAError(Ruby runtime, Throwable cause) { - return Utils.newError(runtime, _PKey(runtime).getClass("RSAError"), cause.getMessage(), cause); + return newRSAError(runtime, cause.getMessage(), cause); + } + + static RaiseException newRSAError(Ruby runtime, String message, Throwable cause) { + return Utils.newError(runtime, _PKey(runtime).getClass("RSAError"), message, cause); } public PKeyRSA(Ruby runtime, RubyClass type) { @@ -124,7 +132,7 @@ public PKeyRSA(Ruby runtime, RubyClass type, RSAPrivateCrtKey privKey, RSAPublic } private volatile RSAPublicKey publicKey; - private volatile transient RSAPrivateCrtKey privateKey; + private volatile transient RSAPrivateKey privateKey; // fields to hold individual RSAPublicKeySpec components. this allows // a public key to be constructed incrementally, as required by the @@ -140,6 +148,7 @@ public PKeyRSA(Ruby runtime, RubyClass type, RSAPrivateCrtKey privKey, RSAPublic private transient volatile BigInteger rsa_dmq1; private transient volatile BigInteger rsa_iqmp; + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; @@ -211,33 +220,27 @@ private static PKeyRSA rsaGenerate(final Ruby runtime, return rsa; } - static PKeyRSA newInstance(final Ruby runtime, final PublicKey publicKey) { - //if ( publicKey instanceof RSAPublicKey ) { - return new PKeyRSA(runtime, (RSAPublicKey) publicKey); - //} - } - @JRubyMethod(rest = true, visibility = Visibility.PRIVATE) - public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { + public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, final Block block) { final Ruby runtime = context.runtime; if ( Arity.checkArgumentCount(runtime, args, 0, 2) == 0 ) { privateKey = null; publicKey = null; return this; } - IRubyObject arg = args[0]; IRubyObject pass = null; - if ( args.length > 1 ) pass = args[1]; + IRubyObject arg = args[0]; + IRubyObject arg1 = args.length > 1 ? args[1] : null; // exponent (Fixnum) or password (String) if ( arg instanceof RubyFixnum ) { int keySize = RubyNumeric.fix2int((RubyFixnum) arg); BigInteger exp = RSAKeyGenParameterSpec.F4; - if ( pass != null && ! pass.isNil() ) { - exp = BigInteger.valueOf(RubyNumeric.num2long(pass)); + if (arg1 != null && !arg1.isNil()) { + exp = BigInteger.valueOf(RubyNumeric.num2long(arg1)); } return rsaGenerate(runtime, this, keySize, exp); } - final char[] passwd = password(pass); + final char[] passwd = password(context, arg1, block); final RubyString str = readInitArg(context, arg); final String strJava = str.toString(); @@ -285,7 +288,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a try { key = readRSAPrivateKey(rsaFactory, str.getBytes()); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (InvalidKeySpecException e) { debug(runtime, "PKeyRSA could not read private key", e); } - catch (IOException e) { debug(runtime, "PKeyRSA could not read private key", e); } + catch (IOException e) { debugStackTrace(runtime, "PKeyRSA could not read private key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyRSA could not read private key", e); else debugStackTrace(runtime, e); @@ -295,7 +298,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a try { key = readRSAPublicKey(rsaFactory, str.getBytes()); } catch (NoClassDefFoundError e) { noClassDef = true; debugStackTrace(runtime, e); } catch (InvalidKeySpecException e) { debug(runtime, "PKeyRSA could not read public key", e); } - catch (IOException e) { debug(runtime, "PKeyRSA could not read public key", e); } + catch (IOException e) { debugStackTrace(runtime, "PKeyRSA could not read public key", e); } catch (RuntimeException e) { if ( isKeyGenerationFailure(e) ) debug(runtime, "PKeyRSA could not read public key", e); else debugStackTrace(runtime, e); @@ -321,8 +324,9 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a } else if ( key instanceof RSAPrivateCrtKey ) { this.privateKey = (RSAPrivateCrtKey) key; + BigInteger exponent = ((RSAPrivateCrtKey) key).getPublicExponent(); try { - this.publicKey = (RSAPublicKey) rsaFactory.generatePublic(new RSAPublicKeySpec(privateKey.getModulus(), privateKey.getPublicExponent())); + this.publicKey = (RSAPublicKey) rsaFactory.generatePublic(new RSAPublicKeySpec(privateKey.getModulus(), exponent)); } catch (GeneralSecurityException e) { throw newRSAError(runtime, e.getMessage()); } catch (RuntimeException e) { @@ -354,22 +358,42 @@ public RubyBoolean private_p() { return getRuntime().newBoolean(isPrivateKey()); } + @JRubyMethod(name = "public_to_der") + public RubyString public_to_der(ThreadContext context) { + final byte[] bytes; + try { + bytes = toDerRSAPublicKey(publicKey); + } + catch (NoClassDefFoundError e) { + throw newRSAError(context.runtime, bcExceptionMessage(e)); + } + catch (Exception e) { + throw newRSAError(getRuntime(), e.getMessage(), e); + } + return StringHelper.newString(context.runtime, bytes); + } + @Override @JRubyMethod(name = "to_der") public RubyString to_der() { final byte[] bytes; try { - bytes = toDerRSAKey(publicKey, privateKey); + bytes = toDerRSAKey(publicKey, privateKey instanceof RSAPrivateCrtKey ? (RSAPrivateCrtKey) privateKey : null); } catch (NoClassDefFoundError e) { throw newRSAError(getRuntime(), bcExceptionMessage(e)); } - catch (IOException e) { - throw newRSAError(getRuntime(), e.getMessage()); + catch (Exception e) { + throw newRSAError(getRuntime(), e.getMessage(), e); } return StringHelper.newString(getRuntime(), bytes); } + @Override + public ASN1Primitive toASN1PublicInfo() { + return toASN1Primitive(publicKey); + } + @JRubyMethod public PKeyRSA public_key() { return new PKeyRSA(getRuntime(), this.publicKey); @@ -379,7 +403,8 @@ public PKeyRSA public_key() { public IRubyObject params(final ThreadContext context) { final Ruby runtime = context.runtime; RubyHash hash = RubyHash.newHash(runtime); - if ( privateKey != null ) { + if (privateKey instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) this.privateKey; hash.op_aset(context, runtime.newString("iqmp"), BN.newBN(runtime, privateKey.getCrtCoefficient())); hash.op_aset(context, runtime.newString("n"), BN.newBN(runtime, privateKey.getModulus())); hash.op_aset(context, runtime.newString("d"), BN.newBN(runtime, privateKey.getPrivateExponent())); @@ -405,7 +430,8 @@ public IRubyObject params(final ThreadContext context) { @JRubyMethod public RubyString to_text() { StringBuilder result = new StringBuilder(); - if (privateKey != null) { + if (privateKey instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) this.privateKey; int len = privateKey.getModulus().bitLength(); result.append("Private-Key: (").append(len).append(" bit)").append('\n'); result.append("modulus:"); @@ -434,30 +460,45 @@ public RubyString to_text() { @Override @JRubyMethod(name = { "to_pem", "to_s" }, alias = "export", rest = true) - public RubyString to_pem(final IRubyObject[] args) { - Arity.checkArgumentCount(getRuntime(), args, 0, 2); + public RubyString to_pem(ThreadContext context, final IRubyObject[] args) { + Arity.checkArgumentCount(context.runtime, args, 0, 2); CipherSpec spec = null; char[] passwd = null; if ( args.length > 0 ) { spec = cipherSpec( args[0] ); - if ( args.length > 1 ) passwd = password(args[1]); + if ( args.length > 1 ) passwd = password(context, args[1], null); } try { final StringWriter writer = new StringWriter(); - if ( privateKey != null ) { - PEMInputOutput.writeRSAPrivateKey(writer, privateKey, spec, passwd); + if (privateKey instanceof RSAPrivateCrtKey) { + PEMInputOutput.writeRSAPrivateKey(writer, (RSAPrivateCrtKey) privateKey, spec, passwd); } else { PEMInputOutput.writeRSAPublicKey(writer, publicKey); } - return RubyString.newString(getRuntime(), writer.getBuffer()); + return RubyString.newString(context.runtime, writer.getBuffer()); } catch (NoClassDefFoundError ncdfe) { - throw newRSAError(getRuntime(), bcExceptionMessage(ncdfe)); + throw newRSAError(context.runtime, bcExceptionMessage(ncdfe)); } catch (IOException ioe) { - throw newRSAError(getRuntime(), ioe.getMessage()); + throw newRSAError(context.runtime, ioe.getMessage()); + } + } + + @JRubyMethod + public RubyString public_to_pem(ThreadContext context) { + try { + final StringWriter writer = new StringWriter(); + PEMInputOutput.writeRSAPublicKey(writer, publicKey); + return RubyString.newString(context.runtime, writer.getBuffer()); + } + catch (NoClassDefFoundError ncdfe) { + throw newRSAError(context.runtime, bcExceptionMessage(ncdfe)); + } + catch (IOException ioe) { + throw newRSAError(context.runtime, ioe.getMessage()); } } @@ -534,6 +575,11 @@ private RubyString doCipherRSA(final Ruby runtime, } } + @JRubyMethod + public IRubyObject oid() { + return getRuntime().newString("rsaEncryption"); + } + @JRubyMethod(name="d=") public synchronized IRubyObject set_d(final ThreadContext context, IRubyObject value) { if ( privateKey != null ) { @@ -596,96 +642,80 @@ public synchronized IRubyObject set_iqmp(final ThreadContext context, IRubyObjec @JRubyMethod(name="iqmp") public synchronized IRubyObject get_iqmp() { - BigInteger iqmp; - if (privateKey != null) { - iqmp = privateKey.getCrtCoefficient(); - } else { - iqmp = rsa_iqmp; - } - if (iqmp != null) { - return BN.newBN(getRuntime(), iqmp); + BigInteger iqmp = this.rsa_iqmp; + if (iqmp == null && privateKey instanceof RSAPrivateCrtKey) { + iqmp = ((RSAPrivateCrtKey) privateKey).getCrtCoefficient(); } + + if (iqmp != null) return BN.newBN(getRuntime(), iqmp); return getRuntime().getNil(); } @JRubyMethod(name="dmp1") public synchronized IRubyObject get_dmp1() { - BigInteger dmp1; - if (privateKey != null) { - dmp1 = privateKey.getPrimeExponentP(); - } else { - dmp1 = rsa_dmp1; - } - if (dmp1 != null) { - return BN.newBN(getRuntime(), dmp1); + BigInteger dmp1 = this.rsa_dmp1; + if (dmp1 == null && privateKey instanceof RSAPrivateCrtKey) { + dmp1 = ((RSAPrivateCrtKey) privateKey).getPrimeExponentP(); } + + if (dmp1 != null) return BN.newBN(getRuntime(), dmp1); return getRuntime().getNil(); } @JRubyMethod(name="dmq1") public synchronized IRubyObject get_dmq1() { - BigInteger dmq1; - if (privateKey != null) { - dmq1 = privateKey.getPrimeExponentQ(); - } else { - dmq1 = rsa_dmq1; - } - if (dmq1 != null) { - return BN.newBN(getRuntime(), dmq1); + BigInteger dmq1 = this.rsa_dmq1; + if (dmq1 == null && privateKey instanceof RSAPrivateCrtKey) { + dmq1 = ((RSAPrivateCrtKey) privateKey).getPrimeExponentQ(); } + + if (dmq1 != null) return BN.newBN(getRuntime(), dmq1); return getRuntime().getNil(); } @JRubyMethod(name="d") public synchronized IRubyObject get_d() { - BigInteger d; - if (privateKey != null) { + final BigInteger d = getPrivateExponent(); + if (d != null) return BN.newBN(getRuntime(), d); + return getRuntime().getNil(); + } + + private BigInteger getPrivateExponent() { + BigInteger d = rsa_d; + if (d == null && privateKey != null) { d = privateKey.getPrivateExponent(); - } else { - d = rsa_d; - } - if (d != null) { - return BN.newBN(getRuntime(), d); } - return getRuntime().getNil(); + return d; } @JRubyMethod(name="p") public synchronized IRubyObject get_p() { - BigInteger p; - if (privateKey != null) { - p = privateKey.getPrimeP(); - } else { - p = rsa_p; - } - if (p != null) { - return BN.newBN(getRuntime(), p); + BigInteger p = rsa_p; + if (p == null && privateKey instanceof RSAPrivateCrtKey) { + p = ((RSAPrivateCrtKey) privateKey).getPrimeP(); } + + if (p != null) return BN.newBN(getRuntime(), p); return getRuntime().getNil(); } @JRubyMethod(name="q") public synchronized IRubyObject get_q() { - BigInteger q; - if (privateKey != null) { - q = privateKey.getPrimeQ(); - } else { - q = rsa_q; - } - if (q != null) { - return BN.newBN(getRuntime(), q); + BigInteger q = rsa_q; + if (q == null && privateKey instanceof RSAPrivateCrtKey) { + q = ((RSAPrivateCrtKey) privateKey).getPrimeQ(); } + + if (q != null) return BN.newBN(getRuntime(), q); return getRuntime().getNil(); } private BigInteger getPublicExponent() { - if (publicKey != null) { - return publicKey.getPublicExponent(); - } else if (privateKey != null) { - return privateKey.getPublicExponent(); - } else { - return rsa_e; - } + if (rsa_e != null) return rsa_e; + + if (publicKey != null) return publicKey.getPublicExponent(); + if (privateKey instanceof RSAPrivateCrtKey) return ((RSAPrivateCrtKey) privateKey).getPublicExponent(); + return null; } @JRubyMethod(name="e") @@ -712,13 +742,11 @@ public synchronized IRubyObject set_e(final ThreadContext context, IRubyObject v } private BigInteger getModulus() { - if (publicKey != null) { - return publicKey.getModulus(); - } else if (privateKey != null) { - return privateKey.getModulus(); - } else { - return rsa_n; - } + if (rsa_n != null) return rsa_n; + + if (publicKey != null) return publicKey.getModulus(); + if (privateKey != null) return privateKey.getModulus(); + return null; } @JRubyMethod(name="n") @@ -744,11 +772,36 @@ public synchronized IRubyObject set_n(final ThreadContext context, IRubyObject v return value; } + @JRubyMethod + public IRubyObject set_key(final ThreadContext context, IRubyObject n, IRubyObject e, IRubyObject d) { + this.rsa_n = BN.getBigInteger(n); + this.rsa_e = BN.getBigInteger(e); + this.rsa_d = BN.getBigInteger(d); + generatePublicKeyIfParams(context); + generatePrivateKeyIfParams(context); + return this; + } + + @JRubyMethod + public IRubyObject set_factors(final ThreadContext context, IRubyObject p, IRubyObject q) { + this.rsa_p = BN.getBigInteger(p); + this.rsa_q = BN.getBigInteger(q); + generatePrivateKeyIfParams(context); + return this; + } + + @JRubyMethod + public IRubyObject set_crt_params(final ThreadContext context, IRubyObject dmp1, IRubyObject dmq1, IRubyObject iqmp) { + this.rsa_dmp1 = BN.asBigInteger(dmp1); + this.rsa_dmq1 = BN.asBigInteger(dmq1); + this.rsa_iqmp = BN.asBigInteger(iqmp); + generatePrivateKeyIfParams(context); + return this; + } + private void generatePublicKeyIfParams(final ThreadContext context) { final Ruby runtime = context.runtime; - if ( publicKey != null ) throw newRSAError(runtime, "illegal modification"); - // Don't access the rsa_n and rsa_e fields directly. They may have // already been consumed and cleared by generatePrivateKeyIfParams. BigInteger _rsa_n = getModulus(); @@ -777,14 +830,13 @@ private void generatePublicKeyIfParams(final ThreadContext context) { private void generatePrivateKeyIfParams(final ThreadContext context) { final Ruby runtime = context.runtime; - if ( privateKey != null ) throw newRSAError(runtime, "illegal modification"); - // Don't access the rsa_n and rsa_e fields directly. They may have // already been consumed and cleared by generatePublicKeyIfParams. - BigInteger _rsa_n = getModulus(); - BigInteger _rsa_e = getPublicExponent(); + final BigInteger rsa_n = getModulus(); + final BigInteger rsa_e = getPublicExponent(); + final BigInteger rsa_d = getPrivateExponent(); - if (_rsa_n != null && _rsa_e != null && rsa_p != null && rsa_q != null && rsa_d != null && rsa_dmp1 != null && rsa_dmq1 != null && rsa_iqmp != null) { + if (rsa_n != null && rsa_e != null && rsa_d != null) { final KeyFactory rsaFactory; try { rsaFactory = SecurityHelper.getKeyFactory("RSA"); @@ -793,17 +845,24 @@ private void generatePrivateKeyIfParams(final ThreadContext context) { throw runtime.newLoadError("unsupported key algorithm (RSA)"); } - try { - privateKey = (RSAPrivateCrtKey) rsaFactory.generatePrivate( - new RSAPrivateCrtKeySpec(_rsa_n, _rsa_e, rsa_d, rsa_p, rsa_q, rsa_dmp1, rsa_dmq1, rsa_iqmp) - ); - } - catch (InvalidKeySpecException e) { - throw newRSAError(runtime, "invalid parameters"); + if (rsa_p != null && rsa_q != null && rsa_dmp1 != null && rsa_dmq1 != null && rsa_iqmp != null) { + try { + privateKey = (RSAPrivateCrtKey) rsaFactory.generatePrivate( + new RSAPrivateCrtKeySpec(rsa_n, rsa_e, rsa_d, rsa_p, rsa_q, rsa_dmp1, rsa_dmq1, rsa_iqmp) + ); + } catch (InvalidKeySpecException e) { + throw newRSAError(runtime, "invalid parameters", e); + } + this.rsa_n = this.rsa_e = this.rsa_d = null; + this.rsa_p = this.rsa_q = null; + this.rsa_dmp1 = this.rsa_dmq1 = this.rsa_iqmp = null; + } else { + try { + privateKey = (RSAPrivateKey) rsaFactory.generatePrivate(new RSAPrivateKeySpec(rsa_n, rsa_d)); + } catch (InvalidKeySpecException e) { + throw newRSAError(runtime, "invalid parameters", e); + } } - rsa_n = null; rsa_e = null; - rsa_d = null; rsa_p = null; rsa_q = null; - rsa_dmp1 = null; rsa_dmq1 = null; rsa_iqmp = null; } } diff --git a/src/main/java/org/jruby/ext/openssl/SSL.java b/src/main/java/org/jruby/ext/openssl/SSL.java index c84d3d47..d58ca9c2 100644 --- a/src/main/java/org/jruby/ext/openssl/SSL.java +++ b/src/main/java/org/jruby/ext/openssl/SSL.java @@ -53,24 +53,40 @@ public class SSL { public static final long OP_ALL = 0x00000FFFL; public static final long OP_NO_TICKET = 0x00004000L; public static final long OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 0x00010000L; + public static final long OP_NO_COMPRESSION = 0x00020000L; public static final long OP_SINGLE_ECDH_USE = 0x00080000L; public static final long OP_SINGLE_DH_USE = 0x00100000L; public static final long OP_EPHEMERAL_RSA = 0x00200000L; public static final long OP_CIPHER_SERVER_PREFERENCE = 0x00400000L; public static final long OP_TLS_ROLLBACK_BUG = 0x00800000L; - public static final long OP_NO_SSLv2 = 0x01000000L; // supported - public static final long OP_NO_SSLv3 = 0x02000000L; // supported - public static final long OP_NO_TLSv1 = 0x04000000L; // supported - public static final long OP_PKCS1_CHECK_1 = 0x08000000L; - public static final long OP_PKCS1_CHECK_2 = 0x10000000L; - public static final long OP_NETSCAPE_CA_DN_BUG = 0x20000000L; - public static final long OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000L; - - public static final int SSL2_VERSION = 1; - public static final int SSL3_VERSION = 768; - public static final int TLS1_VERSION = 769; - public static final int TLS1_1_VERSION = 770; - public static final int TLS1_2_VERSION = 771; + + public static final long OP_NO_SSLv2 = 0x01000000L; + public static final long OP_NO_SSLv3 = 0x02000000L; + public static final long OP_NO_TLSv1 = 0x04000000L; + public static final long OP_NO_TLSv1_2 = 0x08000000L; + public static final long OP_NO_TLSv1_1 = 0x10000000L; + public static final long OP_NO_TLSv1_3 = 0x20000000L; + + // define SSL_OP_NO_SSL_MASK (SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2|SSL_OP_NO_TLSv1_3) + + /* Deprecated in OpenSSL 1.0.1. */ + static final long OP_PKCS1_CHECK_1 = 0x08000000L; + /* Deprecated in OpenSSL 1.0.1. */ + static final long OP_PKCS1_CHECK_2 = 0x10000000L; + /* Deprecated in OpenSSL 1.1.0. */ + static final long OP_NETSCAPE_CA_DN_BUG = 0x20000000L; + /* Deprecated in OpenSSL 1.1.0. */ + static final long OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000L; + + public static final int SSL2_VERSION = 0x0002; + public static final int SSL3_VERSION = 0x0300; + public static final int TLS1_VERSION = 0x0301; + public static final int TLS1_1_VERSION = 0x0302; + public static final int TLS1_2_VERSION = 0x0303; + /* OpenSSL 1.1.1 */ + public static final int TLS1_3_VERSION = 0x0304; + + // define TLS_MAX_VERSION TLS1_3_VERSION private static final String JSSE_TLS_ephemeralDHKeySize = "jdk.tls.ephemeralDHKeySize" ; private static final String JSSE_TLS_ephemeralDHKeySize_default = "matched" ; @@ -142,6 +158,7 @@ static void createSSL(final Ruby runtime, final RubyModule OpenSSL, final RubyCl SSL.setConstant("OP_ALL", runtime.newFixnum(OP_ALL)); SSL.setConstant("OP_NO_TICKET", runtime.newFixnum(OP_NO_TICKET)); SSL.setConstant("OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", runtime.newFixnum(OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)); + SSL.setConstant("OP_NO_COMPRESSION", runtime.newFixnum(OP_NO_COMPRESSION)); SSL.setConstant("OP_SINGLE_ECDH_USE", runtime.newFixnum(OP_SINGLE_ECDH_USE)); SSL.setConstant("OP_SINGLE_DH_USE", runtime.newFixnum(OP_SINGLE_DH_USE)); SSL.setConstant("OP_EPHEMERAL_RSA", runtime.newFixnum(OP_EPHEMERAL_RSA)); @@ -150,6 +167,9 @@ static void createSSL(final Ruby runtime, final RubyModule OpenSSL, final RubyCl SSL.setConstant("OP_NO_SSLv2", runtime.newFixnum(OP_NO_SSLv2)); SSL.setConstant("OP_NO_SSLv3", runtime.newFixnum(OP_NO_SSLv3)); SSL.setConstant("OP_NO_TLSv1", runtime.newFixnum(OP_NO_TLSv1)); + SSL.setConstant("OP_NO_TLSv1_1", runtime.newFixnum(OP_NO_TLSv1_1)); + SSL.setConstant("OP_NO_TLSv1_2", runtime.newFixnum(OP_NO_TLSv1_2)); + SSL.setConstant("OP_NO_TLSv1_3", runtime.newFixnum(OP_NO_TLSv1_3)); SSL.setConstant("OP_PKCS1_CHECK_1", runtime.newFixnum(OP_PKCS1_CHECK_1)); SSL.setConstant("OP_PKCS1_CHECK_2", runtime.newFixnum(OP_PKCS1_CHECK_2)); SSL.setConstant("OP_NETSCAPE_CA_DN_BUG", runtime.newFixnum(OP_NETSCAPE_CA_DN_BUG)); @@ -160,6 +180,7 @@ static void createSSL(final Ruby runtime, final RubyModule OpenSSL, final RubyCl SSL.setConstant("TLS1_VERSION", runtime.newFixnum(TLS1_VERSION)); SSL.setConstant("TLS1_1_VERSION", runtime.newFixnum(TLS1_1_VERSION)); SSL.setConstant("TLS1_2_VERSION", runtime.newFixnum(TLS1_2_VERSION)); + SSL.setConstant("TLS1_3_VERSION", runtime.newFixnum(TLS1_3_VERSION)); SSLContext.createSSLContext(runtime, SSL); SSLSocket.createSSLSocket(runtime, SSL); @@ -210,7 +231,7 @@ private static RaiseException newWaitSSLError(final Ruby runtime, final String n if ( waitErrorBacktrace ) { return Utils.newError(runtime, errorClass, message, false); } - return Utils.newErrorWithoutTrace(runtime, errorClass, message, false); + return Utils.newErrorWithoutTrace(runtime, errorClass, message); } static RubyModule _SSL(final Ruby runtime) { diff --git a/src/main/java/org/jruby/ext/openssl/SSLContext.java b/src/main/java/org/jruby/ext/openssl/SSLContext.java index d6ed0e92..3fc0ef5a 100644 --- a/src/main/java/org/jruby/ext/openssl/SSLContext.java +++ b/src/main/java/org/jruby/ext/openssl/SSLContext.java @@ -45,6 +45,7 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509ExtendedKeyManager; @@ -53,12 +54,15 @@ import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; +import org.jruby.RubyString; +import org.jruby.RubyFixnum; import org.jruby.RubyHash; import org.jruby.RubyInteger; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubySymbol; +import org.jruby.RubyProc; import org.jruby.anno.JRubyMethod; import org.jruby.common.IRubyWarnings.ID; import org.jruby.runtime.Arity; @@ -79,14 +83,14 @@ import org.jruby.ext.openssl.x509store.X509Object; import org.jruby.ext.openssl.x509store.X509Utils; +import static org.jruby.ext.openssl.CipherStrings.SuiteToOSSL; import static org.jruby.ext.openssl.StringHelper.*; import static org.jruby.ext.openssl.SSL.*; -import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.X509Cert._Certificate; +import static org.jruby.ext.openssl.x509store.StoreContext.ossl_ssl_ex_vcb_idx; import static org.jruby.ext.openssl.OpenSSL.debug; import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; import static org.jruby.ext.openssl.OpenSSL.warn; -import static org.jruby.ext.openssl.Utils.hasNonNilInstanceVariable; /** * @author Ola Bini @@ -99,10 +103,14 @@ public class SSLContext extends RubyObject { private static final HashMap SSL_VERSION_OSSL2JSSE; // Mapping table for JSEE's enabled protocols for the algorithm. private static final Map ENABLED_PROTOCOLS; + // Mapping table from CRuby parse_proto_version(VALUE str) + private static final Map PROTO_VERSION_MAP; + + private static final Map JSSE_TO_VERSION; static { - SSL_VERSION_OSSL2JSSE = new LinkedHashMap(20, 1); - ENABLED_PROTOCOLS = new HashMap(8, 1); + SSL_VERSION_OSSL2JSSE = new LinkedHashMap<>(32, 1); + ENABLED_PROTOCOLS = new HashMap<>(16, 1); SSL_VERSION_OSSL2JSSE.put("TLSv1", "TLSv1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_server", "TLSv1"); @@ -123,13 +131,13 @@ public class SSLContext extends RubyObject { SSL_VERSION_OSSL2JSSE.put("SSLv23_server", "SSL"); SSL_VERSION_OSSL2JSSE.put("SSLv23_client", "SSL"); - ENABLED_PROTOCOLS.put("SSL", new String[] { "SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }); + ENABLED_PROTOCOLS.put("SSL", new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" }); // Historically we were ahead of MRI to support TLS // ... thus the non-standard names version names : SSL_VERSION_OSSL2JSSE.put("TLS", "TLS"); - ENABLED_PROTOCOLS.put("TLS", new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" }); + ENABLED_PROTOCOLS.put("TLS", new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" }); SSL_VERSION_OSSL2JSSE.put("TLSv1.1", "TLSv1.1"); SSL_VERSION_OSSL2JSSE.put("TLSv1_1_server", "TLSv1.1"); @@ -140,11 +148,35 @@ public class SSLContext extends RubyObject { SSL_VERSION_OSSL2JSSE.put("TLSv1_2", "TLSv1.2"); // supported on MRI 2.x ENABLED_PROTOCOLS.put("TLSv1.2", new String[] { "TLSv1.2" }); - SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); // just for completeness + SSL_VERSION_OSSL2JSSE.put("TLSv1.2", "TLSv1.2"); SSL_VERSION_OSSL2JSSE.put("TLSv1_2_server", "TLSv1.2"); SSL_VERSION_OSSL2JSSE.put("TLSv1_2_client", "TLSv1.2"); + + SSL_VERSION_OSSL2JSSE.put("TLSv1.3", "TLSv1.3"); + SSL_VERSION_OSSL2JSSE.put("TLSv1_3_server", "TLSv1.3"); + SSL_VERSION_OSSL2JSSE.put("TLSv1_3_client", "TLSv1.3"); + SSL_VERSION_OSSL2JSSE.put("TLSv1_3", "TLSv1.3"); + ENABLED_PROTOCOLS.put("TLSv1.3", new String[] { "TLSv1.3" }); + + PROTO_VERSION_MAP = new HashMap<>(8, 1); + PROTO_VERSION_MAP.put("SSL2", SSL.SSL2_VERSION); + PROTO_VERSION_MAP.put("SSL3", SSL.SSL3_VERSION); + PROTO_VERSION_MAP.put("TLS1", SSL.TLS1_VERSION); + PROTO_VERSION_MAP.put("TLS1_1", SSL.TLS1_1_VERSION); + PROTO_VERSION_MAP.put("TLS1_2", SSL.TLS1_2_VERSION); + PROTO_VERSION_MAP.put("TLS1_3", SSL.TLS1_3_VERSION); + + JSSE_TO_VERSION = new HashMap<>(8, 1); + JSSE_TO_VERSION.put("SSLv2", SSL.SSL2_VERSION); + JSSE_TO_VERSION.put("SSLv3", SSL.SSL3_VERSION); + JSSE_TO_VERSION.put("TLSv1", SSL.TLS1_VERSION); + JSSE_TO_VERSION.put("TLSv1.1", SSL.TLS1_1_VERSION); + JSSE_TO_VERSION.put("TLSv1.2", SSL.TLS1_2_VERSION); + JSSE_TO_VERSION.put("TLSv1.3", SSL.TLS1_3_VERSION); } + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static ObjectAllocator SSLCONTEXT_ALLOCATOR = new ObjectAllocator() { public IRubyObject allocate(Ruby runtime, RubyClass klass) { return new SSLContext(runtime, klass); @@ -157,21 +189,65 @@ public static void createSSLContext(final Ruby runtime, final RubyModule SSL) { final ThreadContext context = runtime.getCurrentContext(); SSLContext.addReadWriteAttribute(context, "cert"); SSLContext.addReadWriteAttribute(context, "key"); + /* + * A certificate or Array of certificates that will be sent to the client. + */ SSLContext.addReadWriteAttribute(context, "client_ca"); + /* + * The path to a file containing a PEM-format CA certificate + */ SSLContext.addReadWriteAttribute(context, "ca_file"); + /* + * The path to a directory containing CA certificates in PEM format. + * + * Files are looked up by subject's X509 name's hash value. + */ SSLContext.addReadWriteAttribute(context, "ca_path"); SSLContext.addReadWriteAttribute(context, "timeout"); SSLContext.addReadWriteAttribute(context, "verify_mode"); + /* + * Number of CA certificates to walk when verifying a certificate chain. + */ SSLContext.addReadWriteAttribute(context, "verify_depth"); + /* + * 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. + */ SSLContext.addReadWriteAttribute(context, "verify_callback"); - SSLContext.addReadWriteAttribute(context, "options"); + /* + * 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=. + */ + SSLContext.addReadWriteAttribute(context, "verify_hostname"); + /* + * An OpenSSL::X509::Store used for certificate verification. + */ SSLContext.addReadWriteAttribute(context, "cert_store"); + /* + * 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. + */ SSLContext.addReadWriteAttribute(context, "extra_chain_cert"); SSLContext.addReadWriteAttribute(context, "client_cert_cb"); SSLContext.addReadWriteAttribute(context, "session_id_context"); SSLContext.addReadWriteAttribute(context, "tmp_dh_callback"); SSLContext.addReadWriteAttribute(context, "servername_cb"); SSLContext.addReadWriteAttribute(context, "renegotiation_cb"); + SSLContext.addReadWriteAttribute(context, "alpn_protocols"); + SSLContext.addReadWriteAttribute(context, "alpn_select_cb"); SSLContext.defineAlias("ssl_timeout", "timeout"); SSLContext.defineAlias("ssl_timeout=", "timeout="); @@ -181,20 +257,13 @@ public static void createSSLContext(final Ruby runtime, final RubyModule SSL) { final Set methodKeys = SSL_VERSION_OSSL2JSSE.keySet(); final RubyArray methods = runtime.newArray( methodKeys.size() ); for ( final String method : methodKeys ) { - if ( method.equals("SSLv2") || method.startsWith("SSLv2_") ) { - continue; // do not report SSLv2, SSLv2_server, SSLv2_client - } if ( method.indexOf('.') == -1 ) { // do not "officially" report TLSv1.1 and TLSv1.2 methods.append( runtime.newSymbol(method) ); } } SSLContext.defineConstant("METHODS", methods); - // in 1.8.7 as well as 1.9.3 : - // [:TLSv1, :TLSv1_server, :TLSv1_client, :SSLv3, :SSLv3_server, :SSLv3_client, :SSLv23, :SSLv23_server, :SSLv23_client] - // in 2.0.0 : - // [:TLSv1, :TLSv1_server, :TLSv1_client, :TLSv1_2, :TLSv1_2_server, :TLSv1_2_client, :TLSv1_1, :TLSv1_1_server, - // :TLSv1_1_client, :SSLv3, :SSLv3_server, :SSLv3_client, :SSLv23, :SSLv23_server, :SSLv23_client] + SSLContext.deprecateConstant(runtime, "METHODS"); SSLContext.setConstant("SESSION_CACHE_OFF", runtime.newFixnum(SESSION_CACHE_OFF)); SSLContext.setConstant("SESSION_CACHE_CLIENT", runtime.newFixnum(SESSION_CACHE_CLIENT)); @@ -204,49 +273,6 @@ public static void createSSLContext(final Ruby runtime, final RubyModule SSL) { SSLContext.setConstant("SESSION_CACHE_NO_INTERNAL_LOOKUP", runtime.newFixnum(SESSION_CACHE_NO_INTERNAL_LOOKUP)); SSLContext.setConstant("SESSION_CACHE_NO_INTERNAL_STORE", runtime.newFixnum(SESSION_CACHE_NO_INTERNAL_STORE)); SSLContext.setConstant("SESSION_CACHE_NO_INTERNAL", runtime.newFixnum(SESSION_CACHE_NO_INTERNAL)); - - // DEFAULT_CERT_STORE = OpenSSL::X509::Store.new - // DEFAULT_CERT_STORE.set_default_paths - // if defined?(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL) - // DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - // end - final X509Store DEFAULT_CERT_STORE = X509Store.newStore(runtime); - DEFAULT_CERT_STORE.set_default_paths(context); - final IRubyObject V_FLAG_CRL_CHECK_ALL = _X509(runtime).getConstantAt("V_FLAG_CRL_CHECK_ALL"); - if ( V_FLAG_CRL_CHECK_ALL != null ) DEFAULT_CERT_STORE.set_flags(V_FLAG_CRL_CHECK_ALL); - - SSLContext.setConstant("DEFAULT_CERT_STORE", DEFAULT_CERT_STORE); - - // DEFAULT_PARAMS = { - // :ssl_version => "SSLv23", - // :verify_mode => OpenSSL::SSL::VERIFY_PEER, - // :ciphers => "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", - // :options => OpenSSL::SSL::OP_ALL, - // } - // on MRI 2.1 (should not matter for us) : - // :options => defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) ? - // OpenSSL::SSL::OP_ALL & ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS : - // OpenSSL::SSL::OP_ALL - final RubyHash DEFAULT_PARAMS = new RubyHash(runtime); - IRubyObject ssl_version = StringHelper.newString(runtime, new byte[] { 'S','S','L','v','2','3' }); - DEFAULT_PARAMS.op_aset(context, runtime.newSymbol("ssl_version"), ssl_version); - IRubyObject verify_mode = runtime.newFixnum(VERIFY_PEER); - DEFAULT_PARAMS.op_aset(context, runtime.newSymbol("verify_mode"), verify_mode); - IRubyObject ciphers = StringHelper.newString(runtime, new byte[] { - 'A','L','L',':', - '!','A','D','H',':', - '!','E','X','P','O','R','T',':', - '!','S','S','L','v','2',':', - 'R','C','4','+','R','S','A',':', - '+','H','I','G','H',':', - '+','M','E','D','I','U','M',':', - '+','L','O','W' - }); - DEFAULT_PARAMS.op_aset(context, runtime.newSymbol("ciphers"), ciphers); - IRubyObject options = runtime.newFixnum(OP_ALL); - DEFAULT_PARAMS.op_aset(context, runtime.newSymbol("options"), options); - - SSLContext.setConstant("DEFAULT_PARAMS", DEFAULT_PARAMS); } static final int SESSION_CACHE_OFF = 0; @@ -267,10 +293,20 @@ public SSLContext(Ruby runtime, RubyClass type) { super(runtime, _SSLContext(runtime)); } + private long options = OP_ALL; + + //private transient CipherStrings.Def cipher_list; + /* same as above but sorted for lookup */ + //private transient CipherStrings.Def cipher_list_by_id; + /* TLSv1.3 specific ciphersuites */ + //private transient CipherStrings.Def tls13_ciphersuites; + private String ciphers = CipherStrings.SSL_DEFAULT_CIPHER_LIST; - private String protocol = "SSL"; // SSLv23 in OpenSSL by default + private String protocol = "SSL"; // ctx->method private boolean protocolForServer = true; private boolean protocolForClient = true; + private int minProtocolVersion = 0; + private int maxProtocolVersion = 0; private PKey t_key; private X509Cert t_cert; @@ -279,35 +315,35 @@ public SSLContext(Ruby runtime, RubyClass type) { //private int sessionCacheMode; // 2 default on MRI private int sessionCacheSize; // 20480 - private InternalContext internalContext; + private volatile InternalContext internalContext; @JRubyMethod(required = 0, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(IRubyObject[] args) { - if ( args.length > 0 ) set_ssl_version(args[0]); + assert this.options == OP_ALL; // self.options |= OpenSSL::SSL::OP_ALL + if ( args.length > 0 ) set_ssl_version(args[0]); // self.ssl_version = version if version return initializeImpl(); } - @Override + @JRubyMethod(visibility = Visibility.PRIVATE) + @Override // NOTE: instance variables (no internal state) on #dup public IRubyObject initialize_copy(IRubyObject original) { - return super.initialize_copy(original); - // NOTE: only instance variables (no internal state) on #dup - // final SSLContext that = (SSLContext) original; - // this.ciphers = that.ciphers; - // return this; + SSLContext copy = (SSLContext) super.initialize_copy(original); + copy.options = ((SSLContext) original).options; + return copy; } final SSLContext initializeImpl() { return this; } @JRubyMethod public IRubyObject setup(final ThreadContext context) { - final Ruby runtime = context.runtime; + if (isFrozen()) return context.nil; + return doSetup(context); + } - if ( isFrozen() ) return runtime.getNil(); + private synchronized IRubyObject doSetup(final ThreadContext context) { + if (isFrozen()) return context.nil; - synchronized(this) { - if ( isFrozen() ) return runtime.getNil(); - this.freeze(context); - } + final Ruby runtime = context.runtime; final X509Store certStore = getCertStore(); @@ -405,9 +441,9 @@ public IRubyObject setup(final ThreadContext context) { value = getInstanceVariable("@verify_callback"); if ( value != null && ! value.isNil() ) { - store.setExtraData(1, value); + store.setExtraData(ossl_ssl_ex_vcb_idx, value); } else { - store.setExtraData(1, null); + store.setExtraData(ossl_ssl_ex_vcb_idx, null); } value = getInstanceVariable("@verify_depth"); @@ -422,6 +458,29 @@ public IRubyObject setup(final ThreadContext context) { // SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb); } + final String[] alpnProtocols; + + value = getInstanceVariable("@alpn_protocols"); + if ( value != null && ! value.isNil() ) { + IRubyObject[] alpn_protocols = ((RubyArray) value).toJavaArrayMaybeUnsafe(); + String[] protocols = new String[alpn_protocols.length]; + for(int i = 0; i < protocols.length; i++) { + protocols[i] = alpn_protocols[i].convertToString().asJavaString(); + } + alpnProtocols = protocols; + } else { + alpnProtocols = null; + } + + final RubyProc alpnSelectCb; + value = getInstanceVariable("@alpn_select_cb"); + if ( value != null && ! value.isNil() ) { + alpnSelectCb = (RubyProc) value; + } else { + alpnSelectCb = null; + } + + // NOTE: no API under javax.net to support session get/new/remove callbacks /* val = ossl_sslctx_get_sess_id_ctx(self); @@ -448,36 +507,50 @@ public IRubyObject setup(final ThreadContext context) { */ try { - internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, verifyMode, timeout); + internalContext = createInternalContext(context, cert, key, store, clientCert, extraChainCert, + verifyMode, timeout, alpnProtocols, alpnSelectCb); } catch (GeneralSecurityException e) { throw newSSLError(runtime, e); } + this.freeze(context); + return runtime.getTrue(); } @JRubyMethod - public RubyArray ciphers(final ThreadContext context) { - return matchedCiphers(context); + public RubyArray ciphers(final ThreadContext context) { // SSL_CTX_get_ciphers + return matchedCiphersWithCache(context, this.ciphers); } - private RubyArray matchedCiphers(final ThreadContext context) { + private RubyArray matchedCiphersWithCache(final ThreadContext context, final String ciphers) { + final CipherListCache cache = cipherListCache; + if ( protocol.equals(cache.protocol) && ciphers.equals(cache.ciphers) ) { + return newSharedArray(cache.cipherList); + } + + final RubyArray match = matchedCiphers(context, ciphers); + cipherListCache = new CipherListCache(protocol, ciphers, match); + return newSharedArray(match); + } + + private RubyArray matchedCiphers(final ThreadContext context, final String ciphers) { final Ruby runtime = context.runtime; try { - final String[] supported = getSupportedCipherSuites(this.protocol); + final String[] supported = getSupportedCipherSuites(context, protocol); final Collection cipherDefs = - CipherStrings.matchingCiphers(this.ciphers, supported, false); + CipherStrings.matchingCiphers(ciphers, supported, false); final IRubyObject[] cipherList = new IRubyObject[ cipherDefs.size() ]; int i = 0; for ( CipherStrings.Def def : cipherDefs ) { cipherList[i++] = runtime.newArrayNoCopy( - newUTF8String(runtime, def.name), // 0 - newUTF8String(runtime, sslVersionString(def.algorithms)), // 1 + newUTF8String(runtime, def.name).freeze(context), // 0 + newUTF8String(runtime, sslVersionString(def.algorithms)).freeze(context), // 1 runtime.newFixnum(def.algStrengthBits), // 2 runtime.newFixnum(def.algBits) // 3 - ); + ).freeze(context); } return runtime.newArrayNoCopy(cipherList); } @@ -486,46 +559,91 @@ private RubyArray matchedCiphers(final ThreadContext context) { } } + private static RubyArray newSharedArray(RubyArray array) { + return (RubyArray) array.dup(); // shares underlying IRubyObject[] values + } + @JRubyMethod(name = "ciphers=") public IRubyObject set_ciphers(final ThreadContext context, final IRubyObject ciphers) { + String cipherString; if ( ciphers.isNil() ) { - this.ciphers = CipherStrings.SSL_DEFAULT_CIPHER_LIST; + cipherString = CipherStrings.SSL_DEFAULT_CIPHER_LIST; } else if ( ciphers instanceof RubyArray ) { final RubyArray ciphs = (RubyArray) ciphers; StringBuilder cipherStr = new StringBuilder(); String sep = ""; for ( int i = 0; i < ciphs.size(); i++ ) { - cipherStr.append(sep).append( ciphs.eltInternal(i).toString() ); + Object elem = ciphs.eltInternal(i); + if (elem instanceof RubyArray) { + elem = ((RubyArray) elem).eltInternal(0); + } else if (elem instanceof RubyString) { + // NOTE: JOSSL allows to pass in Java cipher names (in an array) + String osslName = SuiteToOSSL.get(((RubyString) elem).asJavaString()); + if (osslName != null) elem = osslName; + } + cipherStr.append(sep).append( elem.toString() ); sep = ":"; } - this.ciphers = cipherStr.toString(); + cipherString = cipherStr.toString(); } else { - this.ciphers = ciphers.asString().toString(); + cipherString = ciphers.asString().toString(); } - if ( matchedCiphers(context).isEmpty() ) { + + if (cipherString.equals(CipherStrings.SSL_DEFAULT_CIPHER_LIST)) { + cipherString = CipherStrings.SSL_DEFAULT_CIPHER_LIST; // due caching + } + + if ( matchedCiphersWithCache(context, cipherString).isEmpty() ) { throw newSSLError(context.runtime, "no cipher match"); } + + this.ciphers = cipherString; return ciphers; } @JRubyMethod(name = "ssl_version=") - public IRubyObject set_ssl_version(IRubyObject version) { - final String versionStr; - if ( version instanceof RubySymbol ) { - versionStr = version.toString(); + public IRubyObject set_ssl_version(IRubyObject method) { + final String version; + if ( method instanceof RubySymbol ) { + version = method.toString(); } else { - versionStr = version.convertToString().toString(); + version = method.convertToString().toString(); } - final String protocol = SSL_VERSION_OSSL2JSSE.get(versionStr); + final String protocol = SSL_VERSION_OSSL2JSSE.get(version); if ( protocol == null ) { - throw getRuntime().newArgumentError("unknown SSL method `"+ versionStr +"'"); + throw getRuntime().newArgumentError("unknown SSL method `"+ version +"'"); } this.protocol = protocol; - protocolForServer = ! versionStr.endsWith("_client"); - protocolForClient = ! versionStr.endsWith("_server"); - return version; + protocolForServer = ! version.endsWith("_client"); + protocolForClient = ! version.endsWith("_server"); + return method; + } + + @JRubyMethod(name = "set_minmax_proto_version", visibility = Visibility.PRIVATE) + public IRubyObject set_minmax_proto_version(ThreadContext context, IRubyObject minVersion, IRubyObject maxVersion) { + minProtocolVersion = parseProtoVersion(minVersion); + maxProtocolVersion = parseProtoVersion(maxVersion); + + return context.nil; + } + + private int parseProtoVersion(IRubyObject version) { + if (version.isNil()) + return 0; + if (version instanceof RubyFixnum) { + return RubyFixnum.fix2int(version); + } + + String string = version.asString().asJavaString(); + Integer sslVersion = PROTO_VERSION_MAP.get(string); + + if (sslVersion == null) { + throw getRuntime().newArgumentError("unrecognized version \"" + string + "\""); + } + + return sslVersion; } final String getProtocol() { return this.protocol; } @@ -594,35 +712,67 @@ void setLastVerifyResult(int verifyResult) { this.verifyResult = verifyResult; } - private static String cachedProtocol = null; - private static String[] cachedSupportedCipherSuites; + private static CipherListCache cipherListCache = new CipherListCache(null, null, null); - private static String[] getSupportedCipherSuites(final String protocol) - throws GeneralSecurityException { - if ( cachedProtocol == null ) { - synchronized(SSLContext.class) { - if ( cachedProtocol == null ) { - cachedSupportedCipherSuites = dummySSLEngine(protocol).getSupportedCipherSuites(); - cachedProtocol = protocol; - return cachedSupportedCipherSuites; + private static class CipherListCache { + final String protocol; + final String ciphers; + + final RubyArray cipherList; + + CipherListCache(String protocol, String ciphers, RubyArray cipherList) { + this.protocol = protocol; + this.ciphers = ciphers; + this.cipherList = cipherList; + } + } + + void setApplicationProtocolsOrSelector(final SSLEngine engine) { + setApplicationProtocolSelector(engine); + setApplicationProtocols(engine); + } + + private void setApplicationProtocolSelector(final SSLEngine engine) { + final RubyProc alpn_select_cb = internalContext.alpnSelectCallback; + if (alpn_select_cb != null) { + engine.setHandshakeApplicationProtocolSelector((_engine, protocols) -> { + final Ruby runtime = getRuntime(); + IRubyObject[] rubyProtocols = new IRubyObject[protocols.size()]; + int i = 0; for (String protocol : protocols) { + rubyProtocols[i++] = runtime.newString(protocol); } - } + + IRubyObject[] args = new IRubyObject[] { RubyArray.newArray(runtime, rubyProtocols) }; + IRubyObject selected_protocol = alpn_select_cb.call(runtime.getCurrentContext(), args); + if (selected_protocol != null && !selected_protocol.isNil()) { + return ((RubyString) selected_protocol).asJavaString(); + } + return null; // callback returned nil - none of the advertised names are acceptable + }); } + } - if ( protocol.equals(cachedProtocol) ) return cachedSupportedCipherSuites; + private void setApplicationProtocols(final SSLEngine engine) { + final String[] alpn_protocols = internalContext.alpnProtocols; + if (alpn_protocols != null) { + SSLParameters params = engine.getSSLParameters(); + params.setApplicationProtocols(alpn_protocols); + engine.setSSLParameters(params); + } + } - return dummySSLEngine(protocol).getSupportedCipherSuites(); + private static String[] getSupportedCipherSuites(ThreadContext context, final String protocol) + throws GeneralSecurityException { + return dummySSLEngine(context, protocol).getSupportedCipherSuites(); } - private static SSLEngine dummySSLEngine(final String protocol) throws GeneralSecurityException { + private static SSLEngine dummySSLEngine(ThreadContext context, final String protocol) throws GeneralSecurityException { javax.net.ssl.SSLContext sslContext = SecurityHelper.getSSLContext(protocol); - sslContext.init(null, null, null); + sslContext.init(null, null, OpenSSL.getSecureRandom(context)); return sslContext.createSSLEngine(); } - // should keep SSLContext as a member for introducin SSLSession. later... - final SSLEngine createSSLEngine(String peerHost, int peerPort) - throws NoSuchAlgorithmException, KeyManagementException { + final SSLEngine createSSLEngine(String peerHost, int peerPort) { final SSLEngine engine; // an empty peerHost implies no SNI (RFC 3546) support requested if ( peerHost == null || peerHost.length() == 0 ) { @@ -634,18 +784,18 @@ final SSLEngine createSSLEngine(String peerHost, int peerPort) else { engine = internalContext.getSSLContext().createSSLEngine(peerHost, peerPort); } - engine.setEnabledCipherSuites( getCipherSuites(engine.getSupportedCipherSuites()) ); - engine.setEnabledProtocols( getEnabledProtocols(engine) ); + final String[] protocols = getEnabledProtocols(engine); + engine.setEnabledProtocols(protocols); + engine.setEnabledCipherSuites( getEnabledCipherSuites(engine, protocols) ); + return engine; } - private String[] getCipherSuites(final String[] supported) { - Collection cipherDefs = - CipherStrings.matchingCiphers(this.ciphers, supported, true); + private String[] getEnabledCipherSuites(final SSLEngine engine, final String[] protocols) { + final String[] supported = engine.getSupportedCipherSuites(); + Collection cipherDefs = CipherStrings.matchingCiphers(this.ciphers, supported, true); final String[] result = new String[ cipherDefs.size() ]; int i = 0; - for ( CipherStrings.Def def : cipherDefs ) { - result[ i++ ] = def.getCipherSuite(); - } + for ( CipherStrings.Def def : cipherDefs ) result[ i++ ] = def.getCipherSuite(); return result; } @@ -654,24 +804,32 @@ private String[] getEnabledProtocols(final SSLEngine engine) { if ( enabledProtocols != null ) { final long options = getOptions(); final String[] engineProtocols = engine.getEnabledProtocols(); - final List protocols = new ArrayList(enabledProtocols.length); + final List protocols = new ArrayList<>(enabledProtocols.length); for ( final String enabled : enabledProtocols ) { - if (((options & SSL.OP_NO_SSLv2) != 0) && enabled.equals("SSLv2")) { - continue; - } - if (((options & SSL.OP_NO_SSLv3) != 0) && enabled.equals("SSLv3")) { - continue; - } - if (((options & SSL.OP_NO_TLSv1) != 0) && enabled.equals("TLSv1")) { - continue; - } - for ( final String allowed : engineProtocols ) { - if ( allowed.equals(enabled) ) protocols.add(allowed); - } + int protocolVersion = JSSE_TO_VERSION.get(enabled); + if (minProtocolVersion != 0 && protocolVersion < minProtocolVersion) continue; + if (maxProtocolVersion != 0 && protocolVersion > maxProtocolVersion) continue; + + if (((options & OP_NO_SSLv2) != 0) && enabled.equals("SSLv2")) continue; + if (((options & OP_NO_SSLv3) != 0) && enabled.equals("SSLv3")) continue; + if (((options & OP_NO_TLSv1) != 0) && enabled.equals("TLSv1")) continue; + if (((options & OP_NO_TLSv1_1) != 0) && enabled.equals("TLSv1.1")) continue; + if (((options & OP_NO_TLSv1_2) != 0) && enabled.equals("TLSv1.2")) continue; + if (((options & OP_NO_TLSv1_3) != 0) && enabled.equals("TLSv1.3")) continue; + + if (arrayContains(engineProtocols, enabled)) protocols.add(enabled); } - return protocols.toArray( new String[ protocols.size() ] ); + + return protocols.toArray(EMPTY_STRING_ARRAY); } - return new String[0]; + return EMPTY_STRING_ARRAY; + } + + private static boolean arrayContains(final String[] array, final String value) { + for (final String elem : array) { + if (elem.equals(value)) return true; + } + return false; } private static final byte[] TLSv1 = { 'T','L','S','v','1' }; @@ -749,11 +907,23 @@ private String getCaPath() { } private long getOptions() { - IRubyObject options = getInstanceVariable("@options"); - if ( options != null && ! options.isNil() ) { - return RubyNumeric.fix2long(options); + return options; + } + + @JRubyMethod + public RubyInteger options(ThreadContext context) { + return context.runtime.newFixnum(getOptions()); + } + + @JRubyMethod(name = "options=") + public IRubyObject options_set(final IRubyObject options) { + if (options.isNil()) { + this.options = OP_ALL; + } else { + this.options = RubyNumeric.num2long(options); } - return 0; + + return this; } private static List convertToAuxCerts(final ThreadContext context, IRubyObject value) { @@ -802,8 +972,9 @@ static RubyClass _SSLContext(final Ruby runtime) { private InternalContext createInternalContext(ThreadContext context, final X509Cert xCert, final PKey pKey, final Store store, final List clientCert, final List extraChainCert, - final int verifyMode, final int timeout) throws NoSuchAlgorithmException, KeyManagementException { - InternalContext internalContext = new InternalContext(xCert, pKey, store, clientCert, extraChainCert, verifyMode, timeout); + final int verifyMode, final int timeout, + final String[] alpnProtocols, final RubyProc alpnSelectCb) throws NoSuchAlgorithmException, KeyManagementException { + InternalContext internalContext = new InternalContext(xCert, pKey, store, clientCert, extraChainCert, verifyMode, timeout, alpnProtocols, alpnSelectCb); internalContext.initSSLContext(context); return internalContext; } @@ -820,16 +991,18 @@ private class InternalContext { final List clientCert, final List extraChainCert, final int verifyMode, - final int timeout) throws NoSuchAlgorithmException { + final int timeout, + final String[] alpnProtocols, + final RubyProc alpnSelectCallback) throws NoSuchAlgorithmException { if ( pKey != null && xCert != null ) { this.privateKey = pKey.getPrivateKey(); - this.keyAlgorithm = pKey.getAlgorithm(); + this.keyType = pKey.getKeyType(); this.cert = xCert.getAuxCert(); } else { this.privateKey = null; - this.keyAlgorithm = null; + this.keyType = null; this.cert = null; } @@ -838,6 +1011,8 @@ private class InternalContext { this.extraChainCert = extraChainCert; this.verifyMode = verifyMode; this.timeout = timeout; + this.alpnProtocols = alpnProtocols; + this.alpnSelectCallback = alpnSelectCallback; // initialize SSL context : @@ -852,7 +1027,7 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException { // SSLContext (internals) on Sun JDK : // private final java.security.Provider provider; "SunJSSE" // private final javax.net.ssl.SSLContextSpi; sun.security.ssl.SSLContextImpl - sslContext.init(keyManager, trustManager, OpenSSL.getSecureRandomFrom(context)); + sslContext.init(keyManager, trustManager, OpenSSL.getSecureRandom(context)); // if secureRandom == null JSSE will try : // - new SecureRandom(); // - SecureRandom.getInstance("PKCS11", cryptoProvider); @@ -875,7 +1050,7 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException { final Store store; final X509AuxCertificate cert; - final String keyAlgorithm; + final String keyType; final PrivateKey privateKey; final int verifyMode; @@ -885,6 +1060,9 @@ void initSSLContext(final ThreadContext context) throws KeyManagementException { private final int timeout; + private final String[] alpnProtocols; + private final RubyProc alpnSelectCallback; + private final javax.net.ssl.SSLContext sslContext; // part of ssl_verify_cert_chain @@ -895,9 +1073,9 @@ StoreContext createStoreContext(final String purpose) { if ( storeContext.init(null, null) == 0 ) return null; // for verify_cb - storeContext.setExtraData(1, store.getExtraData(1)); + storeContext.setExtraData(ossl_ssl_ex_vcb_idx, store.getExtraData(ossl_ssl_ex_vcb_idx)); if ( purpose != null ) storeContext.setDefault(purpose); - storeContext.verifyParameter.inherit(store.verifyParameter); + storeContext.getParam().inherit(store.getParam()); return storeContext; } @@ -923,7 +1101,7 @@ public String chooseEngineClientAlias(String[] keyType, java.security.Principal[ if (internalContext.privateKey == null) return null; for (int i = 0; i < keyType.length; i++) { - if (keyType[i].equalsIgnoreCase(internalContext.keyAlgorithm)) { + if (keyType[i].equalsIgnoreCase(internalContext.keyType)) { return keyType[i]; } } @@ -934,7 +1112,7 @@ public String chooseEngineClientAlias(String[] keyType, java.security.Principal[ public String chooseEngineServerAlias(String keyType, java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) { if (internalContext.privateKey == null) return null; - if (keyType.equalsIgnoreCase(internalContext.keyAlgorithm)) { + if (keyType.equalsIgnoreCase(internalContext.keyType)) { return keyType; } return null; @@ -958,7 +1136,7 @@ public java.security.cert.X509Certificate[] getCertificateChain(String alias) { chain = (List) internalContext.extraChainCert; } else if ( internalContext.cert != null ) { - chain = new ArrayList(8); + chain = new ArrayList<>(8); StoreContext storeCtx = internalContext.createStoreContext(null); X509AuxCertificate x = internalContext.cert; @@ -974,7 +1152,7 @@ else if ( internalContext.cert != null ) { if (storeCtx.getBySubject(X509Utils.X509_LU_X509, name, s_obj) <= 0) { break; } - x = ((Certificate) s_obj[0]).x509; + x = ((Certificate) s_obj[0]).cert; } catch (RuntimeException e) { debugStackTrace(e); @@ -1061,14 +1239,14 @@ private void verifyChain(final StoreContext storeContext) throws CertificateExce ok = storeContext.verifyCertificate(); } catch (Exception e) { - internalContext.setLastVerifyResult(storeContext.error); - if ( storeContext.error == X509Utils.V_OK ) { + internalContext.setLastVerifyResult(storeContext.getError()); + if ( storeContext.getError() == X509Utils.V_OK ) { internalContext.setLastVerifyResult(X509Utils.V_ERR_CERT_REJECTED); } throw new CertificateException("certificate verify failed", e); } - internalContext.setLastVerifyResult(storeContext.error); + internalContext.setLastVerifyResult(storeContext.getError()); if ( ok == 0 ) { throw new CertificateException("certificate verify failed"); } diff --git a/src/main/java/org/jruby/ext/openssl/SSLSession.java b/src/main/java/org/jruby/ext/openssl/SSLSession.java index 356fb5a4..5c2a832a 100644 --- a/src/main/java/org/jruby/ext/openssl/SSLSession.java +++ b/src/main/java/org/jruby/ext/openssl/SSLSession.java @@ -35,7 +35,6 @@ import org.jruby.RubyString; import org.jruby.RubyTime; import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -51,14 +50,8 @@ */ public class SSLSession extends RubyObject { - private static final ObjectAllocator SESSION_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new SSLSession(runtime, klass); - } - }; - static void createSession(final Ruby runtime, final RubyModule SSL, final RubyClass OpenSSLError) { // OpenSSL::SSL - RubyClass Session = SSL.defineClassUnder("Session", runtime.getObject(), SESSION_ALLOCATOR); + RubyClass Session = SSL.defineClassUnder("Session", runtime.getObject(), (r, klass) -> new SSLSession(r, klass)); // OpenSSL::SSL::Session::SessionError Session.defineClassUnder("SessionError", OpenSSLError, OpenSSLError.getAllocator()); Session.defineAnnotatedMethods(SSLSession.class); @@ -149,8 +142,10 @@ public IRubyObject set_time(final ThreadContext context, IRubyObject time) { @JRubyMethod(name = "timeout") public IRubyObject timeout(final ThreadContext context) { final SSLSessionContext sessionContext = sslSession().getSessionContext(); - // default in OpenSSL is 300 - if ( sessionContext == null ) return context.runtime.newFixnum(300); + if (sessionContext == null) { + // JDK's default is 24h (default in OpenSSL is 300) + return context.runtime.newFixnum(86400); + } return context.runtime.newFixnum(sessionContext.getSessionTimeout()); } @@ -161,7 +156,11 @@ public IRubyObject set_timeout(final ThreadContext context, IRubyObject timeout) warn(context, "WARNING: can not set OpenSSL::SSL::Session#timeout=("+ timeout +") no session context"); return context.nil; } - sessionContext.setSessionTimeout(RubyNumeric.fix2int(timeout)); // in seconds as well + try { + sessionContext.setSessionTimeout(RubyNumeric.fix2int(timeout)); // in seconds as well + } catch (IllegalArgumentException e) { + throw context.runtime.newArgumentError(e.getMessage()); + } return timeout; } diff --git a/src/main/java/org/jruby/ext/openssl/SSLSocket.java b/src/main/java/org/jruby/ext/openssl/SSLSocket.java index 871be46c..2bcd5658 100644 --- a/src/main/java/org/jruby/ext/openssl/SSLSocket.java +++ b/src/main/java/org/jruby/ext/openssl/SSLSocket.java @@ -28,17 +28,14 @@ package org.jruby.ext.openssl; import java.io.IOException; -import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.ClosedChannelException; +import java.nio.channels.ClosedSelectorException; import java.nio.channels.NotYetConnectedException; -import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; @@ -69,12 +66,6 @@ public class SSLSocket extends RubyObject { private static final long serialVersionUID = -2084816623554406237L; - private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new SSLSocket(runtime, klass); - } - }; - private enum CallSiteIndex { // self @@ -110,7 +101,8 @@ public static void createSSLSocket(final Ruby runtime, final RubyModule SSL) { / } } - RubyClass SSLSocket = runtime.defineClassUnder("SSLSocket", runtime.getObject(), ALLOCATOR, SSL, extraCallSites); + RubyClass SSLSocket = runtime.defineClassUnder("SSLSocket", runtime.getObject(), + (r, klass) -> new SSLSocket(r, klass), SSL, extraCallSites); final ThreadContext context = runtime.getCurrentContext(); @@ -153,14 +145,15 @@ private static CallSite callSite(final CallSite[] sites, final CallSiteIndex ind private SSLEngine engine; private RubyIO io; - private ByteBuffer peerAppData; - private ByteBuffer peerNetData; - private ByteBuffer netData; - private ByteBuffer dummy; + private ByteBuffer appReadData; + private ByteBuffer netReadData; + private ByteBuffer netWriteData; + private final ByteBuffer dummy = ByteBuffer.allocate(0); // could be static private boolean initialHandshake = false; + private transient long initializeTime; - private SSLEngineResult.HandshakeStatus handshakeStatus; + private SSLEngineResult.HandshakeStatus handshakeStatus; // != null after hand-shake starts private SSLEngineResult.Status status; int verifyResult = X509Utils.V_OK; @@ -169,17 +162,19 @@ private static CallSite callSite(final CallSite[] sites, final CallSiteIndex ind public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args) { final Ruby runtime = context.runtime; - if ( Arity.checkArgumentCount(runtime, args, 1, 2) == 1 ) { - sslContext = new SSLContext(runtime).initializeImpl(); + if (Arity.checkArgumentCount(runtime, args, 1, 2) == 1) { + this.sslContext = new SSLContext(runtime).initializeImpl(); } else { - sslContext = (SSLContext) args[1]; + if (!(args[1] instanceof SSLContext)) { + throw runtime.newTypeError(args[1], "OpenSSL::SSL::SSLContext"); + } + this.sslContext = (SSLContext) args[1]; } - if ( ! ( args[0] instanceof RubyIO ) ) { + if (!(args[0] instanceof RubyIO)) { throw runtime.newTypeError("IO expected but got " + args[0].getMetaClass().getName()); } - setInstanceVariable("@context", this.sslContext); // only compat (we do not use @context) - setInstanceVariable("@io", this.io = (RubyIO) args[0]); + setInstanceVariable("@io", this.io = (RubyIO) args[0]); // RubyBasicSocket extends RubyIO set_io_nonblock_checked(context, runtime.getTrue()); // This is a bit of a hack: SSLSocket should share code with // RubyBasicSocket, which always sets sync to true. @@ -187,6 +182,10 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a set_sync(context, runtime.getTrue()); // io.sync = true setInstanceVariable("@sync_close", runtime.getFalse()); // self.sync_close = false sslContext.setup(context); + setInstanceVariable("@context", sslContext); // only compat (we do not use @context) + + this.initializeTime = System.currentTimeMillis(); + return Utils.invokeSuper(context, this, args, Block.NULL_BLOCK); // super() } @@ -208,8 +207,9 @@ private IRubyObject fallback_set_io_nonblock_checked(ThreadContext context, Ruby return context.nil; } - private SSLEngine ossl_ssl_setup(final ThreadContext context) - throws NoSuchAlgorithmException, KeyManagementException { + private static final String SESSION_SOCKET_ID = "socket_id"; + + private SSLEngine ossl_ssl_setup(final ThreadContext context, final boolean server) { SSLEngine engine = this.engine; if ( engine != null ) return engine; @@ -221,15 +221,18 @@ private SSLEngine ossl_ssl_setup(final ThreadContext context) engine = sslContext.createSSLEngine(peerHost, peerPort); final javax.net.ssl.SSLSession session = engine.getSession(); - peerNetData = ByteBuffer.allocate(session.getPacketBufferSize()); - peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize()); - netData = ByteBuffer.allocate(session.getPacketBufferSize()); - peerNetData.limit(0); - peerAppData.limit(0); - netData.limit(0); - dummy = ByteBuffer.allocate(0); + netReadData = ByteBuffer.allocate(session.getPacketBufferSize()); + appReadData = ByteBuffer.allocate(session.getApplicationBufferSize()); + netWriteData = ByteBuffer.allocate(session.getPacketBufferSize()); + netReadData.limit(0); + appReadData.limit(0); + netWriteData.limit(0); + this.engine = engine; copySessionSetupIfSet(context); + + sslContext.setApplicationProtocolsOrSelector(engine); + return engine; } @@ -239,6 +242,15 @@ private SSLEngine ossl_ssl_setup(final ThreadContext context) @JRubyMethod(name = "context") public final SSLContext context() { return this.sslContext; } + @JRubyMethod(name = "alpn_protocol") + public IRubyObject alpn_protocol(final ThreadContext context) { + final String protocol = engine.getApplicationProtocol(); + // null if it has not yet been determined if alpn might be used for this connection, + // an empty String if application protocols values will not be used, + if (protocol == null || protocol.isEmpty()) return context.nil; + return RubyString.newString(context.runtime, protocol); + } + @JRubyMethod(name = "sync") public IRubyObject sync(final ThreadContext context) { final CallSite[] sites = getMetaClass().getExtraCallSites(); @@ -284,7 +296,7 @@ private IRubyObject connectImpl(final ThreadContext context, final boolean block try { if ( ! initialHandshake ) { - SSLEngine engine = ossl_ssl_setup(context); + SSLEngine engine = ossl_ssl_setup(context, false); engine.setUseClientMode(true); engine.beginHandshake(); handshakeStatus = engine.getHandshakeStatus(); @@ -301,16 +313,6 @@ private IRubyObject connectImpl(final ThreadContext context, final boolean block forceClose(); throw newSSLErrorFromHandshake(context.runtime, e); } - catch (NoSuchAlgorithmException e) { - debugStackTrace(context.runtime, e); - forceClose(); - throw newSSLError(context.runtime, e); - } - catch (KeyManagementException e) { - debugStackTrace(context.runtime, e); - forceClose(); - throw newSSLError(context.runtime, e); - } catch (IOException e) { //debugStackTrace(context.runtime, e); forceClose(); @@ -354,7 +356,7 @@ private IRubyObject acceptImpl(final ThreadContext context, final boolean blocki try { if ( ! initialHandshake ) { - final SSLEngine engine = ossl_ssl_setup(context); + final SSLEngine engine = ossl_ssl_setup(context, true); engine.setUseClientMode(false); final IRubyObject verify_mode = verify_mode(context); if ( verify_mode != context.nil ) { @@ -391,14 +393,6 @@ private IRubyObject acceptImpl(final ThreadContext context, final boolean blocki } throw newSSLErrorFromHandshake(context.runtime, e); } - catch (NoSuchAlgorithmException e) { - debugStackTrace(context.runtime, e); - throw newSSLError(context.runtime, e); - } - catch (KeyManagementException e) { - debugStackTrace(context.runtime, e); - throw newSSLError(context.runtime, e); - } catch (IOException e) { debugStackTrace(context.runtime, e); throw newSSLError(context.runtime, e); @@ -459,9 +453,9 @@ private Object waitSelect(final int operations, final boolean blocking, final bo try { result[0] = selector.selectNow(); - if ( result[0] == 0 ) { + if (result[0] == 0) { if ((operations & SelectionKey.OP_READ) != 0 && (operations & SelectionKey.OP_WRITE) != 0) { - if ( key.isReadable() ) { + if (key.isReadable()) { writeWouldBlock(runtime, exception, result); } //else if ( key.isWritable() ) { @@ -470,27 +464,27 @@ private Object waitSelect(final int operations, final boolean blocking, final bo else { //neither, pick one readWouldBlock(runtime, exception, result); } - } - else if ((operations & SelectionKey.OP_READ) != 0) { + } else if ((operations & SelectionKey.OP_READ) != 0) { readWouldBlock(runtime, exception, result); - } - else if ((operations & SelectionKey.OP_WRITE) != 0) { + } else if ((operations & SelectionKey.OP_WRITE) != 0) { writeWouldBlock(runtime, exception, result); } } - } - catch (IOException ioe) { - throw runtime.newRuntimeError("Error with selector: " + ioe.getMessage()); + } catch (ClosedSelectorException ex) { + throw Utils.newRuntimeError(runtime, "selector closed", ex); + } catch (IOException ex) { + throw Utils.newIOError(runtime, ex); } } else { io.addBlockingThread(thread); thread.executeBlockingTask(new RubyThread.BlockingTask() { - public void run() throws InterruptedException { + public void run() { try { result[0] = selector.select(); - } - catch (IOException ioe) { - throw runtime.newRuntimeError("Error with selector: " + ioe.getMessage()); + } catch (ClosedSelectorException ex) { + throw Utils.newRuntimeError(runtime, "selector closed", ex); + } catch (IOException ex) { + throw Utils.newIOError(runtime, ex); } } @@ -512,32 +506,27 @@ public void wakeup() { //JRuby <= 9.1.2.0 that makes this not always the case, so we have to check return selector.selectedKeys().contains(key) ? Boolean.TRUE : Boolean.FALSE; } - } - catch (InterruptedException interrupt) { return Boolean.FALSE; } - finally { - // Note: I don't like ignoring these exceptions, but it's - // unclear how likely they are to happen or what damage we - // might do by ignoring them. Note that the pieces are separate - // so that we can ensure one failing does not affect the others - // running. + } catch (InterruptedException interrupt) { + debug(runtime, "SSLSocket.waitSelect", interrupt); + return Boolean.FALSE; + } finally { + // Note: I don't like ignoring these exceptions, but it's unclear how likely they are to happen or what + // damage we might do by ignoring them. Note that the pieces are separate so that we can ensure one failing + // does not affect the others running. // clean up the key in the selector try { if ( key != null ) key.cancel(); if ( selector != null ) selector.selectNow(); - } - catch (Exception e) { // ignore - debugStackTrace(runtime, e); + } catch (Exception e) { // ignore + debugStackTrace(runtime, "SSLSocket.waitSelect (ignored)", e); } // shut down and null out the selector try { - if ( selector != null ) { - runtime.getSelectorPool().put(selector); - } - } - catch (Exception e) { // ignore - debugStackTrace(runtime, e); + if ( selector != null ) runtime.getSelectorPool().put(selector); + } catch (Exception e) { // ignore + debugStackTrace(runtime, "SSLSocket.waitSelect (ignored)", e); } if (blocking) { @@ -579,7 +568,6 @@ private IRubyObject doHandshake(final boolean blocking, final boolean exception) } // otherwise, proceed as before - switch (handshakeStatus) { case FINISHED: case NOT_HANDSHAKING: @@ -592,23 +580,30 @@ private IRubyObject doHandshake(final boolean blocking, final boolean exception) if (readAndUnwrap(blocking) == -1 && handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED) { throw new SSLHandshakeException("Socket closed"); } - // during initialHandshake, calling readAndUnwrap that results UNDERFLOW - // does not mean writable. we explicitly wait for readable channel to avoid - // busy loop. + // during initialHandshake, calling readAndUnwrap that results UNDERFLOW does not mean writable. + // we explicitly wait for readable channel to avoid busy loop. if (initialHandshake && status == SSLEngineResult.Status.BUFFER_UNDERFLOW) { sel = waitSelect(SelectionKey.OP_READ, blocking, exception); if ( sel instanceof IRubyObject ) return (IRubyObject) sel; // :wait_readable } break; case NEED_WRAP: - if ( netData.hasRemaining() ) { + if ( netWriteData.hasRemaining() ) { while ( flushData(blocking) ) { /* loop */ } } - netData.clear(); - SSLEngineResult result = engine.wrap(dummy, netData); - handshakeStatus = result.getHandshakeStatus(); - netData.flip(); + assert !netWriteData.hasRemaining(); + doWrap(blocking); flushData(blocking); + assert status != SSLEngineResult.Status.BUFFER_UNDERFLOW; + if (status == SSLEngineResult.Status.BUFFER_OVERFLOW) { + netWriteData.compact(); + netWriteData = Utils.ensureCapacity(netWriteData, engine.getSession().getPacketBufferSize()); + netWriteData.flip(); + if (handshakeStatus != SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { + sel = waitSelect(SelectionKey.OP_WRITE, blocking, exception); + if ( sel instanceof IRubyObject ) return (IRubyObject) sel; // :wait_writeable + } + } break; default: throw new IllegalStateException("Unknown handshaking status: " + handshakeStatus); @@ -616,6 +611,18 @@ private IRubyObject doHandshake(final boolean blocking, final boolean exception) } } + private void doWrap(final boolean blocking) throws IOException { + netWriteData.clear(); + SSLEngineResult result = engine.wrap(dummy, netWriteData); + netWriteData.flip(); + handshakeStatus = result.getHandshakeStatus(); + status = result.getStatus(); + if (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK + && status == SSLEngineResult.Status.OK) { + doTasks(); + } + } + private void doTasks() { Runnable task; while ((task = engine.getDelegatedTask()) != null) { @@ -625,18 +632,20 @@ private void doTasks() { verifyResult = sslContext.getLastVerifyResult(); } + /** + * @param blocking + * @return whether buffer has remaining data + * @throws IOException + */ private boolean flushData(boolean blocking) throws IOException { try { - writeToChannel(netData, blocking); + writeToChannel(netWriteData, blocking); } catch (IOException ioe) { - netData.position(netData.limit()); + netWriteData.position(netWriteData.limit()); throw ioe; } - if ( netData.hasRemaining() ) { - return true; - } - return false; + return netWriteData.hasRemaining(); } private int writeToChannel(ByteBuffer buffer, boolean blocking) throws IOException { @@ -650,18 +659,20 @@ private int writeToChannel(ByteBuffer buffer, boolean blocking) throws IOExcepti private void finishInitialHandshake() { initialHandshake = false; + + final javax.net.ssl.SSLSession session = engine.getSession(); + if (session.getValue(SESSION_SOCKET_ID) != null) { + session.putValue(SESSION_SOCKET_ID, getObjectId()); + } } - + + // NOTE: gets called on negotiation connect/accept - not really on RE-negotiation as intended?! private void callRenegotiationCallback(final ThreadContext context) throws RaiseException { IRubyObject renegotiationCallback = sslContext.getInstanceVariable("@renegotiation_cb"); - if(renegotiationCallback == null || renegotiationCallback.isNil()) { - return; - } - else { - // the return of the Proc is not important - // Can throw ruby exception to "disallow" renegotiations - renegotiationCallback.callMethod(context, "call", this); - } + if (renegotiationCallback == null || renegotiationCallback.isNil()) return; + // the return of the Proc is not important + // Can throw ruby exception to "disallow" re-negotiations + renegotiationCallback.callMethod(context, "call", this); } public int write(ByteBuffer src, boolean blocking) throws SSLException, IOException { @@ -674,15 +685,15 @@ public int write(ByteBuffer src, boolean blocking) throws SSLException, IOExcept if ( ! blocking ) channel.configureBlocking(false); try { - if ( netData.hasRemaining() ) { + if ( netWriteData.hasRemaining() ) { flushData(blocking); } - netData.clear(); - final SSLEngineResult result = engine.wrap(src, netData); + netWriteData.clear(); + final SSLEngineResult result = engine.wrap(src, netWriteData); if ( result.getStatus() == SSLEngineResult.Status.CLOSED ) { throw getRuntime().newIOError("closed SSL engine"); } - netData.flip(); + netWriteData.flip(); flushData(blocking); return result.bytesConsumed(); } @@ -695,22 +706,22 @@ public int read(final ByteBuffer dst, final boolean blocking) throws IOException if ( initialHandshake ) return 0; if ( engine.isInboundDone() ) return -1; - if ( ! peerAppData.hasRemaining() ) { + if ( ! appReadData.hasRemaining() ) { int appBytesProduced = readAndUnwrap(blocking); if (appBytesProduced == -1 || appBytesProduced == 0) { return appBytesProduced; } } - int limit = Math.min(peerAppData.remaining(), dst.remaining()); - peerAppData.get(dst.array(), dst.arrayOffset(), limit); + int limit = Math.min(appReadData.remaining(), dst.remaining()); + appReadData.get(dst.array(), dst.arrayOffset(), limit); dst.position(dst.arrayOffset() + limit); return limit; } private int readAndUnwrap(final boolean blocking) throws IOException { - final int bytesRead = socketChannelImpl().read(peerNetData); + final int bytesRead = socketChannelImpl().read(netReadData); if ( bytesRead == -1 ) { - if ( ! peerNetData.hasRemaining() || + if ( ! netReadData.hasRemaining() || ( status == SSLEngineResult.Status.BUFFER_UNDERFLOW ) ) { closeInbound(); return -1; @@ -719,12 +730,12 @@ private int readAndUnwrap(final boolean blocking) throws IOException { // be defered till the last engine.unwrap() call. // peerNetData could not be empty. } - peerAppData.clear(); - peerNetData.flip(); + appReadData.clear(); + netReadData.flip(); SSLEngineResult result; do { - result = engine.unwrap(peerNetData, peerAppData); + result = engine.unwrap(netReadData, appReadData); } while ( result.getStatus() == SSLEngineResult.Status.OK && result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP && @@ -733,15 +744,15 @@ private int readAndUnwrap(final boolean blocking) throws IOException { if ( result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED ) { finishInitialHandshake(); } - if ( peerAppData.position() == 0 && + if ( appReadData.position() == 0 && result.getStatus() == SSLEngineResult.Status.OK && - peerNetData.hasRemaining() ) { - result = engine.unwrap(peerNetData, peerAppData); + netReadData.hasRemaining() ) { + result = engine.unwrap(netReadData, appReadData); } status = result.getStatus(); handshakeStatus = result.getHandshakeStatus(); - if ( bytesRead == -1 && ! peerNetData.hasRemaining() ) { + if ( bytesRead == -1 && ! netReadData.hasRemaining() ) { // now it's safe to call closeInbound(). closeInbound(); } @@ -750,15 +761,15 @@ private int readAndUnwrap(final boolean blocking) throws IOException { return -1; } - peerNetData.compact(); - peerAppData.flip(); + netReadData.compact(); + appReadData.flip(); if ( ! initialHandshake && ( handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_TASK || handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP || handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED ) ) { doHandshake(blocking); } - return peerAppData.remaining(); + return appReadData.remaining(); } private void closeInbound() { @@ -773,35 +784,42 @@ private void closeInbound() { } private void doShutdown() throws IOException { - if ( engine.isOutboundDone() ) return; + if (engine.isOutboundDone()) return; - netData.clear(); + if (flushData(false)) { + debug(getRuntime(), "SSLSocket.doShutdown data in the data buffer - can't send close"); + return; + } + netWriteData.clear(); try { - engine.wrap(dummy, netData); + engine.wrap(dummy, netWriteData); // send close (after sslEngine.closeOutbound) } catch (SSLException e) { debug(getRuntime(), "SSLSocket.doShutdown", e); return; } catch (RuntimeException e) { - debugStackTrace(getRuntime(), e); + debugStackTrace(getRuntime(), "SSLSocket.doShutdown", e); return; } - netData.flip(); + netWriteData.flip(); flushData(true); } - private IRubyObject sysreadImpl(final ThreadContext context, - IRubyObject len, IRubyObject buff, final boolean blocking, final boolean exception) { + /** + * @return the (@link RubyString} buffer or :wait_readable / :wait_writeable {@link RubySymbol} + */ + private IRubyObject sysreadImpl(final ThreadContext context, final IRubyObject len, final IRubyObject buff, + final boolean blocking, final boolean exception) { final Ruby runtime = context.runtime; final int length = RubyNumeric.fix2int(len); final RubyString buffStr; - if ( buff != null && ! buff.isNil() ) { - buffStr = buff.asString(); + if ( !buff.isNil() ) { + buffStr = buff.convertToString(); } else { - buffStr = RubyString.newEmptyString(runtime); // fine since we're setValue + buffStr = RubyString.newEmptyString(runtime); // fine since we'll setValue } if ( length == 0 ) { buffStr.clear(); @@ -813,7 +831,7 @@ private IRubyObject sysreadImpl(final ThreadContext context, try { // So we need to make sure to only block when there is no data left to process - if ( engine == null || ! ( peerAppData.hasRemaining() || peerNetData.position() > 0 ) ) { + if ( engine == null || ! ( appReadData.hasRemaining() || netReadData.position() > 0 ) ) { final Object ex = waitSelect(SelectionKey.OP_READ, blocking, exception); if ( ex instanceof IRubyObject ) return (IRubyObject) ex; // :wait_readable } @@ -830,7 +848,7 @@ private IRubyObject sysreadImpl(final ThreadContext context, if ( read == -1 ) { if ( exception ) throw runtime.newEOFError(); - return runtime.getNil(); + return context.nil; } if ( read == 0 && status == SSLEngineResult.Status.BUFFER_UNDERFLOW ) { @@ -847,14 +865,15 @@ private IRubyObject sysreadImpl(final ThreadContext context, buffStr.setValue(new ByteList(bytesRead, offset, read, false)); return buffStr; } - catch (IOException ioe) { - throw runtime.newIOError(ioe.getMessage()); + catch (IOException ex) { + debugStackTrace(runtime, "SSLSocket.sysreadImpl", ex); + throw Utils.newError(runtime::newIOErrorFromException, ex); } } @JRubyMethod public IRubyObject sysread(ThreadContext context, IRubyObject len) { - return sysreadImpl(context, len, null, true, true); + return sysreadImpl(context, len, context.nil, true, true); } @JRubyMethod @@ -876,14 +895,14 @@ public IRubyObject sysread(ThreadContext context, IRubyObject[] args) { @JRubyMethod public IRubyObject sysread_nonblock(ThreadContext context, IRubyObject len) { - return sysreadImpl(context, len, null, false, true); + return sysreadImpl(context, len, context.nil, false, true); } @JRubyMethod public IRubyObject sysread_nonblock(ThreadContext context, IRubyObject len, IRubyObject arg) { if ( arg instanceof RubyHash ) { // exception: false // NOTE: on Ruby 2.3 this is expected to raise a TypeError (but not on 2.2) - return sysreadImpl(context, len, null, false, getExceptionOpt(context, arg)); + return sysreadImpl(context, len, context.nil, false, getExceptionOpt(context, arg)); } return sysreadImpl(context, len, arg, false, true); // buffer arg } @@ -930,8 +949,9 @@ private IRubyObject syswriteImpl(final ThreadContext context, return runtime.newFixnum(written); } - catch (IOException ioe) { - throw runtime.newIOError(ioe.getMessage()); + catch (IOException ex) { + debugStackTrace(runtime, "SSLSocket.syswriteImpl", ex); + throw Utils.newError(runtime::newIOErrorFromException, ex); } } @@ -989,7 +1009,7 @@ private void close(boolean force) { engine.closeOutbound(); - if ( ! force && netData.hasRemaining() ) return; + if ( ! force && netWriteData.hasRemaining() ) return; try { doShutdown(); @@ -1062,15 +1082,15 @@ public IRubyObject peer_cert_chain(final ThreadContext context) { if ( engine == null ) return runtime.getNil(); try { - javax.security.cert.Certificate[] certs = engine.getSession().getPeerCertificateChain(); + Certificate[] certs = engine.getSession().getPeerCertificates(); IRubyObject[] cert_chain = new IRubyObject[ certs.length ]; for ( int i = 0; i < certs.length; i++ ) { cert_chain[i] = X509Cert.wrap(context, certs[i]); } return runtime.newArrayNoCopy(cert_chain); } - catch (javax.security.cert.CertificateEncodingException e) { - throw X509Cert.newCertificateError(getRuntime(), e); + catch (CertificateEncodingException e) { + throw X509Cert.newCertificateError(runtime, e); } catch (SSLPeerUnverifiedException e) { if (runtime.isVerbose() || OpenSSL.isDebug(runtime)) { @@ -1115,7 +1135,7 @@ private boolean reusableSSLEngine() { if ( engine != null ) { final String peerHost = engine.getPeerHost(); if ( peerHost != null && peerHost.length() > 0 ) { - // NOT getSSLContext().createSSLEngine() - no hints for session reuse + // getSSLContext().createSSLEngine() - no hints for session reuse return true; } } @@ -1124,14 +1144,23 @@ private boolean reusableSSLEngine() { @JRubyMethod(name = "session_reused?") public IRubyObject session_reused_p() { - if ( reusableSSLEngine() ) { - if ( ! engine.getEnableSessionCreation() ) { + if (reusableSSLEngine()) { + if (!engine.getEnableSessionCreation()) { // if session creation is disabled we can be sure its to be re-used return getRuntime().getTrue(); } - //return getRuntime().getFalse(); // NOTE: likely incorrect (we can not decide) + // return getRuntime().getFalse(); // incorrect (we can not decide) + } + javax.net.ssl.SSLSession session = sslSession(); + if (!isNullSession(session)) { + if (session.getCreationTime() < this.initializeTime) { + return getRuntime().getTrue(); + } + Object socketId = session.getValue(SESSION_SOCKET_ID); + if (socketId != null && ((Long) socketId).longValue() != getObjectId()) { + return getRuntime().getTrue(); + } } - //warn(getRuntime().getCurrentContext(), "WARNING: SSLSocket#session_reused? is not supported"); return getRuntime().getNil(); // can not decide - probably not } @@ -1141,6 +1170,10 @@ final javax.net.ssl.SSLSession sslSession() { return engine == null ? null : engine.getSession(); } + static boolean isNullSession(final javax.net.ssl.SSLSession session) { + return session == null || "SSL_NULL_WITH_NULL_NULL".equals(session.getCipherSuite()); + } + private transient SSLSession session; @JRubyMethod(name = "session") diff --git a/src/main/java/org/jruby/ext/openssl/SecurityHelper.java b/src/main/java/org/jruby/ext/openssl/SecurityHelper.java index 0bc3f8e2..e2b91e3a 100644 --- a/src/main/java/org/jruby/ext/openssl/SecurityHelper.java +++ b/src/main/java/org/jruby/ext/openssl/SecurityHelper.java @@ -57,6 +57,7 @@ import java.security.cert.X509CRL; import java.security.interfaces.DSAParams; import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.util.Locale; import java.util.Map; @@ -85,11 +86,13 @@ import org.bouncycastle.crypto.params.DSAPublicKeyParameters; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.jce.provider.X509CRLObject; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.OperatorException; import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder; +import org.bouncycastle.operator.bc.BcECContentVerifierProviderBuilder; import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder; import org.jruby.util.SafePropertyAccessor; @@ -110,28 +113,6 @@ public abstract class SecurityHelper { static boolean setJsseProvider = true; static volatile Provider jsseProvider; - /** - * inject under a given name a cipher. also ensures that the registered - * classes are getting used. - * - * @param name the name under which the class gets registered - * @param clazz the CipherSpi class - */ - public static void addCipher(String name, Class clazz) { - implEngines.put("Cipher:" + name, clazz); - tryCipherInternal = true; - } - - /** - * inject under a given name a signature - * - * @param name the name under which the class gets registered - * @param clazz the SignaturSpi class - */ - public static void addSignature(String name, Class clazz) { - implEngines.put("Signature:" + name, clazz); - } - public static Provider getSecurityProvider() { Provider provider = securityProvider; if ( setBouncyCastleProvider && provider == null ) { @@ -266,10 +247,7 @@ static CertificateFactory getCertificateFactory(final String type, final Provide throws CertificateException { final CertificateFactorySpi spi = (CertificateFactorySpi) getImplEngine("CertificateFactory", type); if ( spi == null ) throw new CertificateException(type + " not found"); - return newInstance(CertificateFactory.class, - new Class[]{ CertificateFactorySpi.class, Provider.class, String.class }, - new Object[]{ spi, provider, type } - ); + return CertificateFactory.getInstance(type, provider); } /** @@ -287,12 +265,7 @@ public static KeyFactory getKeyFactory(final String algorithm) static KeyFactory getKeyFactory(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - KeyFactorySpi spi = (KeyFactorySpi) getImplEngine("KeyFactory", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - return newInstance(KeyFactory.class, - new Class[] { KeyFactorySpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); + return KeyFactory.getInstance(algorithm, provider); } /** @@ -311,28 +284,7 @@ public static KeyPairGenerator getKeyPairGenerator(final String algorithm) @SuppressWarnings("unchecked") static KeyPairGenerator getKeyPairGenerator(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final Object spi = getImplEngine("KeyPairGenerator", algorithm); - if ( spi == null ) { - throw new NoSuchAlgorithmException(algorithm + " KeyPairGenerator not available"); - } - - final KeyPairGenerator keyPairGenerator; - if ( spi instanceof KeyPairGenerator ) { - keyPairGenerator = (KeyPairGenerator) spi; - } - else { - final Class delegate; - try { - delegate = (Class) - Class.forName(KeyPairGenerator.class.getName() + "$Delegate"); - } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - - keyPairGenerator = newInstance(delegate, - new Class[] { KeyPairGeneratorSpi.class, String.class }, spi, algorithm - ); - } - setField(keyPairGenerator, KeyPairGenerator.class, "provider", provider); - return keyPairGenerator; + return KeyPairGenerator.getInstance(algorithm, provider); } /** @@ -358,36 +310,20 @@ static KeyStore getKeyStore(final String type, final Provider provider) */ public static MessageDigest getMessageDigest(final String algorithm) throws NoSuchAlgorithmException { try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException nsae) { + // try reflective logic final Provider provider = getSecurityProviderIfAccessible(); if ( provider != null ) return getMessageDigest(algorithm, provider); + + throw nsae; // give up } - catch (NoSuchAlgorithmException e) { } - return MessageDigest.getInstance(algorithm); } @SuppressWarnings("unchecked") static MessageDigest getMessageDigest(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final Object spi = getImplEngine("MessageDigest", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - - final MessageDigest messageDigest; - if ( spi instanceof MessageDigest ) { - messageDigest = (MessageDigest) spi; - } - else { - final Class delegate; - try { - delegate = (Class) - Class.forName(MessageDigest.class.getName() + "$Delegate"); - } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - - messageDigest = newInstance(delegate, - new Class[] { MessageDigestSpi.class, String.class }, spi, algorithm - ); - } - setField(messageDigest, MessageDigest.class, "provider", provider); - return messageDigest; + return MessageDigest.getInstance(algorithm, provider); } public static SecureRandom getSecureRandom() { @@ -401,18 +337,12 @@ public static SecureRandom getSecureRandom() { } } catch (NoSuchAlgorithmException e) { } - return new SecureRandom(); // likely "SHA1PRNG" from SPI sun.security.provider.SecureRandom + return new SecureRandom(); } private static SecureRandom getSecureRandom(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final SecureRandomSpi spi = (SecureRandomSpi) getImplEngine("SecureRandom", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - - return newInstance(SecureRandom.class, - new Class[] { SecureRandomSpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); + return SecureRandom.getInstance(algorithm, provider); } // NOTE: none (at least for BC 1.47) @@ -484,7 +414,6 @@ private static Cipher getCipherInternal(String transformation, final Provider pr spi = (CipherSpi) getImplEngine("Cipher", algorithm); if ( spi == null ) { - // if ( silent ) return null; throw new NoSuchAlgorithmException(transformation + " not found"); } @@ -503,14 +432,9 @@ private static Cipher getCipherInternal(String transformation, final Provider pr } try { // this constructor does not verify the provider - Cipher cipher = newInstance(Cipher.class, - new Class[] { CipherSpi.class, String.class }, - new Object[] { spi, transformation } - ); - setField(cipher, Cipher.class, "provider", provider); - return cipher; + return Cipher.getInstance(transformation, provider); } - catch( Exception e ) { + catch (Exception e) { // TODO now seems like a redundant left over // this constructor does verify the provider which might fail return newInstance(Cipher.class, new Class[] { CipherSpi.class, Provider.class, String.class }, @@ -534,25 +458,7 @@ public static Signature getSignature(final String algorithm) throws NoSuchAlgori @SuppressWarnings("unchecked") static Signature getSignature(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final Object spi = getImplEngine("Signature", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " Signature not available"); - - final Signature signature; - if ( spi instanceof Signature ) { - signature = (Signature) spi; - } else { - final Class delegate; - try { - delegate = (Class) - Class.forName(Signature.class.getName() + "$Delegate"); - } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - - signature = newInstance(delegate, - new Class[] { SignatureSpi.class, String.class }, spi, algorithm - ); - } - setField(signature, Signature.class, "provider", provider); - return signature; + return Signature.getInstance(algorithm, provider); } /** @@ -575,15 +481,13 @@ static Mac getMac(final String algorithm, final Provider provider) private static Mac getMac(final String algorithm, final Provider provider, boolean silent) throws NoSuchAlgorithmException { - MacSpi spi = (MacSpi) getImplEngine("Mac", algorithm); - if ( spi == null ) { + try { + return Mac.getInstance(algorithm, provider); + } + catch (NoSuchAlgorithmException e) { if ( silent ) return null; - throw new NoSuchAlgorithmException(algorithm + " not found"); + throw e; } - return newInstance(Mac.class, - new Class[] { MacSpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); } /** @@ -601,13 +505,7 @@ public static KeyGenerator getKeyGenerator(final String algorithm) throws NoSuch static KeyGenerator getKeyGenerator(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final KeyGeneratorSpi spi = (KeyGeneratorSpi) getImplEngine("KeyGenerator", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - - return newInstance(KeyGenerator.class, - new Class[] { KeyGeneratorSpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); + return KeyGenerator.getInstance(algorithm, provider); } /** @@ -625,13 +523,7 @@ public static KeyAgreement getKeyAgreement(final String algorithm) throws NoSuch static KeyAgreement getKeyAgreement(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final KeyAgreementSpi spi = (KeyAgreementSpi) getImplEngine("KeyAgreement", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - - return newInstance(KeyAgreement.class, - new Class[] { KeyAgreementSpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); + return KeyAgreement.getInstance(algorithm, provider); } /** @@ -649,13 +541,7 @@ public static SecretKeyFactory getSecretKeyFactory(final String algorithm) throw static SecretKeyFactory getSecretKeyFactory(final String algorithm, final Provider provider) throws NoSuchAlgorithmException { - final SecretKeyFactorySpi spi = (SecretKeyFactorySpi) getImplEngine("SecretKeyFactory", algorithm); - if ( spi == null ) throw new NoSuchAlgorithmException(algorithm + " not found"); - - return newInstance(SecretKeyFactory.class, - new Class[] { SecretKeyFactorySpi.class, Provider.class, String.class }, - new Object[] { spi, provider, algorithm } - ); + return SecretKeyFactory.getInstance(algorithm, provider); } private static final String providerSSLContext; // NOTE: experimental support for using BCJSSE @@ -720,19 +606,26 @@ static boolean verify(final X509CRL crl, final PublicKey publicKey, final boolea try { final DigestAlgorithmIdentifierFinder digestAlgFinder = new DefaultDigestAlgorithmIdentifierFinder(); final ContentVerifierProvider verifierProvider; - if ( "DSA".equalsIgnoreCase( publicKey.getAlgorithm() )) { + if (publicKey instanceof DSAPublicKey) { BigInteger y = ((DSAPublicKey) publicKey).getY(); DSAParams params = ((DSAPublicKey) publicKey).getParams(); DSAParameters parameters = new DSAParameters(params.getP(), params.getQ(), params.getG()); AsymmetricKeyParameter dsaKey = new DSAPublicKeyParameters(y, parameters); verifierProvider = new BcDSAContentVerifierProviderBuilder(digestAlgFinder).build(dsaKey); } - else { + else if (publicKey instanceof ECPublicKey) { + AsymmetricKeyParameter ecKey = ECUtil.generatePublicKeyParameter(publicKey); + verifierProvider = new BcECContentVerifierProviderBuilder(digestAlgFinder).build(ecKey); + } + else if (publicKey instanceof RSAPublicKey) { BigInteger mod = ((RSAPublicKey) publicKey).getModulus(); BigInteger exp = ((RSAPublicKey) publicKey).getPublicExponent(); AsymmetricKeyParameter rsaKey = new RSAKeyParameters(false, mod, exp); verifierProvider = new BcRSAContentVerifierProviderBuilder(digestAlgFinder).build(rsaKey); } + else { + throw new IllegalStateException("unsupported public key type: " + (publicKey != null ? publicKey.getClass() : null)); + } return new X509CRLHolder(crl.getEncoded()).isSignatureValid( verifierProvider ); } catch (OperatorException e) { @@ -741,7 +634,7 @@ static boolean verify(final X509CRL crl, final PublicKey publicKey, final boolea catch (CertException e) { throw new SignatureException(e); } - // can happen if the input is DER but does not match expected strucure + // can happen if the input is DER but does not match expected structure catch (ClassCastException e) { throw new SignatureException(e); } @@ -815,8 +708,6 @@ private static Object findImplEngine(final String baseName, String algorithm) { } } - // the obligratory "reflection crap" : - private static T newInstance(Class klass, Class[] paramTypes, Object... params) { final Constructor constructor; try { diff --git a/src/main/java/org/jruby/ext/openssl/StringHelper.java b/src/main/java/org/jruby/ext/openssl/StringHelper.java index 18f620e3..39800eee 100644 --- a/src/main/java/org/jruby/ext/openssl/StringHelper.java +++ b/src/main/java/org/jruby/ext/openssl/StringHelper.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Locale; +import org.jcodings.specific.ASCIIEncoding; import org.jcodings.specific.UTF8Encoding; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -61,6 +62,10 @@ static RubyString newString(final Ruby runtime, final byte[] bytes, final int co return RubyString.newString(runtime, byteList); } + static RubyString newString(final Ruby runtime, final CharSequence chars) { + return new RubyString(runtime, runtime.getString(), chars, ASCIIEncoding.INSTANCE); + } + static ByteList setByteListShared(final RubyString str) { str.setByteListShared(); return str.getByteList(); @@ -129,15 +134,6 @@ static RubyString readInput(final ThreadContext context, final IRubyObject arg) static final ByteList NEW_LINE = new ByteList(new byte[] { '\n' }, false); static final ByteList COMMA_SPACE = new ByteList(new byte[] { ',',' ' }, false); - static void gsub(final Ruby runtime, final ByteList str, final byte match, final byte replace) { - final int begin = str.getBegin(); - final int slen = str.getRealSize(); - final byte[] bytes = str.getUnsafeBytes(); - for ( int i = begin; i < begin + slen; i++ ) { - if ( bytes[i] == match ) bytes[i] = replace; - } - } - static final char[] S20 = new char[] { ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', diff --git a/src/main/java/org/jruby/ext/openssl/Utils.java b/src/main/java/org/jruby/ext/openssl/Utils.java index 2e443765..1afc2ffd 100644 --- a/src/main/java/org/jruby/ext/openssl/Utils.java +++ b/src/main/java/org/jruby/ext/openssl/Utils.java @@ -28,7 +28,9 @@ package org.jruby.ext.openssl; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.HashSet; +import java.util.function.Function; import org.jruby.*; import org.jruby.exceptions.RaiseException; @@ -47,17 +49,25 @@ final class Utils { private Utils() {} static RaiseException newIOError(Ruby runtime, IOException e) { - RaiseException ex = newIOError(runtime, e.getMessage()); - ex.initCause(e); - return ex; + return newIOError(runtime, e.getMessage(), e); } static RaiseException newIOError(Ruby runtime, String msg) { return new RaiseException(runtime, runtime.getIOError(), msg, true); } + static RaiseException newIOError(Ruby runtime, String msg, Exception e) { + RaiseException ex = newIOError(runtime, msg); + ex.initCause(e); + return ex; + } + static RaiseException newRuntimeError(Ruby runtime, Exception e) { - RaiseException ex = newRuntimeError(runtime, e.getMessage()); + return newRuntimeError(runtime, e.getMessage(), e); + } + + static RaiseException newRuntimeError(Ruby runtime, String msg, Exception e) { + RaiseException ex = newRuntimeError(runtime, msg); ex.initCause(e); return ex; } @@ -70,9 +80,9 @@ static RaiseException newRuntimeError(Ruby runtime, String msg) { return new RaiseException(runtime, runtime.getRuntimeError(), msg, true); } - static RaiseException newErrorWithoutTrace(Ruby runtime, RubyClass errorClass, String message, boolean nativeException) { + static RaiseException newErrorWithoutTrace(Ruby runtime, RubyClass errorClass, String message) { final IRubyObject backtrace = runtime.newEmptyArray(); // runtime.getNil(); - return new RaiseException(runtime, errorClass, message, backtrace, nativeException); + return new RaiseException(runtime, errorClass, message, backtrace, false); } static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, boolean nativeException) { @@ -93,9 +103,10 @@ static RaiseException newError(Ruby runtime, RubyClass errorClass, String msg, T return ex; } - static boolean hasNonNilInstanceVariable(final IRubyObject self, final String var) { - final IRubyObject val = self.getInstanceVariables().getInstanceVariable(var); - return val != null && ! val.isNil(); + static RaiseException newError(Function errorFunction, T e) { + RaiseException ex = errorFunction.apply(e); + ex.initCause(e); + return ex; } // reinvented parts of org.jruby.runtime.Helpers for compatibility with "older" JRuby : @@ -181,8 +192,12 @@ public void visit(IRubyObject key, IRubyObject value) { return ret; } - static IRubyObject extractKeywordArg(ThreadContext context, String keyword, RubyHash opts) { - return opts.op_aref(context, context.runtime.newSymbol(keyword)); + static ByteBuffer ensureCapacity(final ByteBuffer buffer, final int size) { + if (size <= buffer.capacity()) return buffer; + buffer.flip(); + ByteBuffer newBuffer = ByteBuffer.allocate(size); + newBuffer.put(buffer); + return newBuffer; } }// Utils diff --git a/src/main/java/org/jruby/ext/openssl/X509Attribute.java b/src/main/java/org/jruby/ext/openssl/X509Attribute.java index 51e0fc48..abff135b 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Attribute.java +++ b/src/main/java/org/jruby/ext/openssl/X509Attribute.java @@ -44,7 +44,6 @@ import org.jruby.RubyObject; import org.jruby.anno.JRubyMethod; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.Visibility; @@ -59,14 +58,8 @@ public class X509Attribute extends RubyObject { private static final long serialVersionUID = 5569940260019783275L; - private static ObjectAllocator ATTRIBUTE_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Attribute(runtime, klass); - } - }; - static void createAttribute(Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass Attribute = X509.defineClassUnder("Attribute", runtime.getObject(), ATTRIBUTE_ALLOCATOR); + RubyClass Attribute = X509.defineClassUnder("Attribute", runtime.getObject(), (r, klass) -> new X509Attribute(r, klass)); X509.defineClassUnder("AttributeError", OpenSSLError, OpenSSLError.getAllocator()); @@ -127,6 +120,7 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a return this; } + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; diff --git a/src/main/java/org/jruby/ext/openssl/X509CRL.java b/src/main/java/org/jruby/ext/openssl/X509CRL.java index a1bec72d..b5baa2b8 100644 --- a/src/main/java/org/jruby/ext/openssl/X509CRL.java +++ b/src/main/java/org/jruby/ext/openssl/X509CRL.java @@ -59,6 +59,8 @@ import org.bouncycastle.cert.X509v2CRLBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.util.Strings; + import org.joda.time.DateTime; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -74,7 +76,6 @@ import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.Variable; @@ -93,14 +94,8 @@ public class X509CRL extends RubyObject { private static final long serialVersionUID = -2463300006179688577L; - private static ObjectAllocator X509CRL_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509CRL(runtime, klass); - } - }; - static void createX509CRL(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass CRL = X509.defineClassUnder("CRL", runtime.getObject(), X509CRL_ALLOCATOR); + RubyClass CRL = X509.defineClassUnder("CRL", runtime.getObject(), (r, klass) -> new X509CRL(r, klass)); X509.defineClassUnder("CRLError", OpenSSLError, OpenSSLError.getAllocator()); CRL.defineAnnotatedMethods(X509CRL.class); } @@ -309,7 +304,7 @@ public IRubyObject initialize_copy(final IRubyObject obj) { public IRubyObject to_pem(final ThreadContext context) { StringWriter writer = new StringWriter(); try { - PEMInputOutput.writeX509CRL(writer, crl); + PEMInputOutput.writeX509CRL(writer, getCRL()); return RubyString.newString(context.runtime, writer.getBuffer()); } catch (IOException e) { @@ -544,7 +539,7 @@ public IRubyObject add_extension(final IRubyObject extension) { @JRubyMethod public IRubyObject sign(final ThreadContext context, final IRubyObject key, IRubyObject digest) { final Ruby runtime = context.runtime; - final String signatureAlgorithm = getSignatureAlgorithm(runtime, (PKey) key, (Digest) digest); + final String signatureAlgorithm = getSignatureAlgorithm(runtime, (PKey) key, digest); final X500Name issuerName = ((X509Name) issuer).getX500Name(); final java.util.Date thisUpdate = getLastUpdate().toDate(); @@ -645,19 +640,23 @@ public IRubyObject sign(final ThreadContext context, final IRubyObject key, IRub return this; } - private String getSignatureAlgorithm(final Ruby runtime, final PKey key, final Digest digest) { + private static String getSignatureAlgorithm(final Ruby runtime, final PKey key, final IRubyObject digest) { // Have to obey some artificial constraints of the OpenSSL implementation. Stupid. final String keyAlg = key.getAlgorithm(); - final String digAlg = digest.getShortAlgorithm(); + final String digAlg; + if (digest instanceof Digest) { + digAlg = ((Digest) digest).getShortAlgorithm(); + } else { + digAlg = Strings.toUpperCase(digest.convertToString().toString()); + } if ( "DSA".equalsIgnoreCase(keyAlg) ) { - if ( ( "MD5".equalsIgnoreCase( digAlg ) ) ) { // || - // ( "SHA1".equals( digest.name().toString() ) ) ) { + if ( ( "MD5".equalsIgnoreCase( digAlg ) ) ) { throw newCRLError(runtime, "unsupported key / digest algorithm ("+ key +" / "+ digAlg +")"); } } else if ( "RSA".equalsIgnoreCase(keyAlg) ) { - if ( "DSS1".equals( digest.name().toString() ) ) { + if ( "DSS1".equals(digAlg) || (digest instanceof Digest && "DSS1".equals(((Digest) digest).name().toString())) ) { throw newCRLError(runtime, "unsupported key / digest algorithm ("+ key +" / "+ digAlg +")"); } } @@ -709,6 +708,14 @@ public IRubyObject op_equal(ThreadContext context, IRubyObject obj) { return context.runtime.getFalse(); } + @Override + public Object toJava(Class target) { + if (target.isAssignableFrom(java.security.cert.X509CRL.class)) { + return getCRL(); + } + return super.toJava(target); + } + private static RubyClass _CRLError(final Ruby runtime) { return _X509(runtime).getClass("CRLError"); } diff --git a/src/main/java/org/jruby/ext/openssl/X509Cert.java b/src/main/java/org/jruby/ext/openssl/X509Cert.java index 991873db..5d4fe48c 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Cert.java +++ b/src/main/java/org/jruby/ext/openssl/X509Cert.java @@ -32,7 +32,6 @@ import java.io.StringWriter; import java.math.BigInteger; -import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; @@ -42,9 +41,9 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; + import java.security.interfaces.DSAPublicKey; import java.security.interfaces.RSAPublicKey; - import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; @@ -60,6 +59,12 @@ import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.joda.time.DateTime; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -77,7 +82,6 @@ import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.ext.openssl.x509store.X509AuxCertificate; import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -100,14 +104,8 @@ public class X509Cert extends RubyObject { private static final long serialVersionUID = -6524431607032364369L; - private static ObjectAllocator X509CERT_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Cert(runtime, klass); - } - }; - static void createX509Cert(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass Certificate = X509.defineClassUnder("Certificate", runtime.getObject(), X509CERT_ALLOCATOR); + RubyClass Certificate = X509.defineClassUnder("Certificate", runtime.getObject(), (r, klass) -> new X509Cert(r, klass)); X509.defineClassUnder("CertificateError", OpenSSLError, OpenSSLError.getAllocator()); Certificate.defineAnnotatedMethods(X509Cert.class); } @@ -133,7 +131,7 @@ private X509Cert(Ruby runtime) { private transient PKey public_key; // lazy initialized - private final List extensions = new ArrayList(); + private final List extensions = new ArrayList<>(4); private boolean changed = true; @@ -147,11 +145,16 @@ final X509AuxCertificate getAuxCert() { public static IRubyObject wrap(Ruby runtime, Certificate cert) throws CertificateEncodingException { - return wrap(runtime.getCurrentContext(), cert.getEncoded()); + return wrap(runtime.getCurrentContext(), cert); } static X509Cert wrap(ThreadContext context, Certificate cert) throws CertificateEncodingException { + if (cert instanceof X509Certificate) { + final X509Cert c = new X509Cert(context.runtime); + c.initialize(context, (X509Certificate) cert); + return c; + } return wrap(context, cert.getEncoded()); } @@ -161,23 +164,14 @@ public static IRubyObject wrap(Ruby runtime, javax.security.cert.Certificate cer return wrap(runtime.getCurrentContext(), cert.getEncoded()); } - static X509Cert wrap(ThreadContext context, javax.security.cert.Certificate cert) - throws javax.security.cert.CertificateEncodingException { - return wrap(context, cert.getEncoded()); - } - static X509Cert wrap(final ThreadContext context, final byte[] encoded) { - //final Ruby runtime = context.runtime; - //final RubyString enc = StringHelper.newString(runtime, encoded); - //return _Certificate(runtime).callMethod(context, "new", enc); final X509Cert cert = new X509Cert(context.runtime); cert.initialize(context, encoded); return cert; } @JRubyMethod(name="initialize", optional = 1, visibility = Visibility.PRIVATE) - public IRubyObject initialize(final ThreadContext context, - final IRubyObject[] args, final Block unusedBlock) { + public IRubyObject initialize(final ThreadContext context, final IRubyObject[] args, final Block unusedBlock) { if ( args.length == 0 ) { this.subject = X509Name.newName(context.runtime); @@ -197,22 +191,28 @@ private void initialize(final ThreadContext context, final byte[] encoded) { } private void initialize(final ThreadContext context, final byte[] encoded, final int offset, final int length) { - final Ruby runtime = context.runtime; - byte[] bytes = StringHelper.readX509PEM(encoded, offset, length); - + final X509Certificate cert; try { final ByteArrayInputStream bis = new ByteArrayInputStream(bytes); cert = (X509Certificate) SecurityHelper.getCertificateFactory("X.509").generateCertificate(bis); } catch (CertificateException e) { - throw newCertificateError(runtime, e); + throw newCertificateError(context.runtime, e); } + initialize(context, cert); + } + + private void initialize(final ThreadContext context, final X509Certificate cert) { + final Ruby runtime = context.runtime; + if ( cert == null ) { throw newCertificateError(runtime, (String) null); } + this.cert = cert; + set_serial( RubyNumeric.str2inum(runtime, runtime.newString(cert.getSerialNumber().toString()), 10) ); set_not_before( context, RubyTime.newTime( runtime, cert.getNotBefore().getTime() ) ); set_not_after( context, RubyTime.newTime( runtime, cert.getNotAfter().getTime() ) ); @@ -280,6 +280,10 @@ static RaiseException newCertificateError(final Ruby runtime, String msg) { return Utils.newError(runtime, _CertificateError(runtime), msg); } + static RaiseException newCertificateError(final Ruby runtime, String msg, Exception e) { + return Utils.newError(runtime, _CertificateError(runtime), msg, e); + } + @Override @JRubyMethod(visibility = Visibility.PRIVATE) public IRubyObject initialize_copy(IRubyObject obj) { @@ -354,7 +358,7 @@ public IRubyObject to_text(final ThreadContext context) { final PublicKey publicKey = getPublicKey(); text.append(S20,0,12).append("Public Key Algorithm: ").append(publicKey.getAlgorithm()).append('\n'); - if ( "RSA".equals( publicKey.getAlgorithm() ) ) { + if (publicKey instanceof RSAPublicKey) { final RSAPublicKey rsaKey = ((RSAPublicKey) publicKey); text.append(S20,0,16).append("Public-Key: (").append( rsaKey.getModulus().bitLength() ).append(" bit)\n"); @@ -365,13 +369,13 @@ public IRubyObject to_text(final ThreadContext context) { text.append(S20,0,16).append("Exponent: ").append(exponent). append(" (0x").append( exponent.toString(16) ).append(")\n"); } - else if ( "DSA".equals( publicKey.getAlgorithm() ) ) { + else if (publicKey instanceof DSAPublicKey) { final DSAPublicKey dsaKey = ((DSAPublicKey) publicKey); text.append(S20,0,16).append("Public-Key: (").append( dsaKey.getY().bitLength() ).append(" bit)\n"); text.append(S20,0,16).append("TODO: not-implemented (PR HOME-WORK)").append('\n'); // left-TODO } - else { + else { // "EC" or "ECDSA" text.append(S20,0,16).append("TODO: not-implemented (PRs WELCOME!)").append('\n'); // left-TODO } @@ -507,7 +511,7 @@ DateTime getNotAfter() { @JRubyMethod public IRubyObject public_key(final ThreadContext context) { if ( public_key == null ) initializePublicKey(); - return public_key.callMethod(context, "public_key"); + return public_key; } @JRubyMethod(name = "public_key=") @@ -535,27 +539,7 @@ private void initializePublicKey() throws RaiseException { throw newCertificateError(runtime, "no certificate"); } - final PublicKey publicKey = cert.getPublicKey(); - - final String algorithm = publicKey.getAlgorithm(); - - if ( "RSA".equalsIgnoreCase(algorithm) ) { - //if ( public_key == null ) { - // throw new IllegalStateException("no public key encoded data"); - //} - set_public_key( PKeyRSA.newInstance(runtime, publicKey) ); - } - else if ( "DSA".equalsIgnoreCase(algorithm) ) { - //if ( public_key == null ) { - // throw new IllegalStateException("no public key encoded data"); - //} - set_public_key( PKeyDSA.newInstance(runtime, publicKey) ); - } - else { - String message = "unsupported algorithm"; - if ( algorithm != null ) message += " '" + algorithm + "'"; - throw newCertificateError(runtime, message); - } + set_public_key(PKey.newInstance(runtime, cert.getPublicKey())); this.changed = changed; } @@ -564,69 +548,93 @@ else if ( "DSA".equalsIgnoreCase(algorithm) ) { public IRubyObject sign(final ThreadContext context, final IRubyObject key, final IRubyObject digest) { final Ruby runtime = context.runtime; + if (!(key instanceof PKey)) { // MRI: NoMethodError: undefined method `private?' for nil:NilClass + throw runtime.newTypeError(key, PKey._PKey(runtime).getClass("PKey")); + } + // Have to obey some artificial constraints of the OpenSSL implementation. Stupid. final String keyAlg = ((PKey) key).getAlgorithm(); final String digAlg; final String digName; if (digest instanceof Digest) { digAlg = ((Digest) digest).getShortAlgorithm(); - digName = ((Digest) digest).name().toString(); + digName = ((Digest) digest).getName(); } - else { + else if (digest instanceof RubyString) { digAlg = digest.asJavaString(); digName = null; } + else { // MRI: TypeError: wrong argument type nil (expected OpenSSL/Digest) + throw runtime.newTypeError(digest, Digest._Digest(runtime)); + } if( ( "DSA".equalsIgnoreCase(keyAlg) && "MD5".equalsIgnoreCase(digAlg) ) || ( "RSA".equalsIgnoreCase(keyAlg) && "DSS1".equals(digName) ) ) { throw newCertificateError(runtime, "signature_algorithm not supported"); } - org.bouncycastle.x509.X509V3CertificateGenerator builder = getCertificateBuilder(); - + final X509v3CertificateBuilder builder = newCertificateBuilder(); for ( X509Extension ext : uniqueExtensions() ) { try { final byte[] bytes = ext.getRealValueEncoded(); - builder.addExtension(ext.getRealObjectID().getId(), ext.isRealCritical(), bytes); + builder.addExtension(ext.getRealObjectID(), ext.isRealCritical(), bytes); } - catch (IOException ioe) { - throw runtime.newIOErrorFromException(ioe); + catch (IOException e) { + throw newCertificateError(runtime, "invalid extension (" + e.getMessage() + ")", e); } } - builder.setSignatureAlgorithm(digAlg + "WITH" + keyAlg); // "SHA1WITHRSA" - + final X509CertificateHolder certHolder; try { - cert = builder.generate( ((PKey) key).getPrivateKey() ); - } - catch (GeneralSecurityException e) { + ContentSigner signer = + new JcaContentSignerBuilder(digAlg + "WITH" + keyAlg). + build(((PKey) key).getPrivateKey()); + certHolder = builder.build(signer); + } catch (OperatorCreationException e) { + Exception cause = (Exception) e.getCause(); // GeneralSecurityException + if (cause == null) cause = e; + throw newCertificateError(runtime, "cannot create signer: " + cause.getMessage(), cause); + } catch (IllegalStateException e) { + // e.g. "not all mandatory fields set in V3 TBScertificate generator" + throw newCertificateError(runtime, "could not generate certificate", e); + } catch (RuntimeException e) { throw newCertificateError(runtime, e); } - if (cert == null) throw newCertificateError(runtime, (String) null); + try { + this.cert = (X509Certificate) + SecurityHelper.getCertificateFactory("X.509"). + generateCertificate(new ByteArrayInputStream(certHolder.getEncoded())); + } catch (IOException|CertificateException e) { + throw newCertificateError(runtime, "could not re-generate certificate", e); + } - String name = ASN1Registry.o2a(cert.getSigAlgOID()); + String name = ASN1Registry.o2a(certHolder.getSignatureAlgorithm().getAlgorithm()); if ( name == null ) name = cert.getSigAlgOID(); this.sig_alg = runtime.newString(name); this.changed = false; return this; } - private org.bouncycastle.x509.X509V3CertificateGenerator getCertificateBuilder() { - org.bouncycastle.x509.X509V3CertificateGenerator generator = - new org.bouncycastle.x509.X509V3CertificateGenerator(); - generator.setSerialNumber( serial.abs() ); - - if ( subject != null ) generator.setSubjectDN( ((X509Name) subject).getRealName() ); - if ( issuer != null ) generator.setIssuerDN( ((X509Name) issuer).getRealName() ); + private X509v3CertificateBuilder newCertificateBuilder() { + //if ( serial.equals(BigInteger.ZERO) ) { // NOTE: diversion from MRI (OpenSSL allows not setting serial) + // throw newCertificateError(getRuntime(), "Certificate#serial needs to be set (to > 0)"); + //} - generator.setNotBefore( not_before.getJavaDate() ); - generator.setNotAfter( not_after.getJavaDate() ); - generator.setPublicKey( getPublicKey() ); + SubjectPublicKeyInfo publicKeyInfo; + try { + publicKeyInfo = SubjectPublicKeyInfo.getInstance(public_key.getPublicKey().getEncoded()); + } catch (Exception e) { + throw newCertificateError(getRuntime(), "invalid public key data", e); + } - return generator; + return new X509v3CertificateBuilder( + issuer == null ? null : ((X509Name) issuer).getX500Name(), + serial.abs(), + not_before.getJavaDate(), not_after.getJavaDate(), + subject == null ? null : ((X509Name) subject).getX500Name(), + publicKeyInfo + ); } - //private transient org.bouncycastle.x509.X509V3CertificateGenerator generator; - @JRubyMethod public RubyBoolean verify(final IRubyObject key) { final Ruby runtime = getRuntime(); @@ -638,23 +646,15 @@ public RubyBoolean verify(final IRubyObject key) { return runtime.getTrue(); } catch (CertificateException e) { - debug(runtime, "Certificate#verify failed: ", e); + debugStackTrace(runtime, "X509Cert#verify", e); throw newCertificateError(runtime, e); } - catch (NoSuchAlgorithmException e) { + catch (NoSuchProviderException|NoSuchAlgorithmException e) { debugStackTrace(runtime, e); throw newCertificateError(runtime, e); } - catch (NoSuchProviderException e) { - debugStackTrace(runtime, e); - throw newCertificateError(runtime, e); - } - catch (SignatureException e) { - debug(runtime, "Certificate#verify failed: ", e); - return runtime.getFalse(); - } - catch (InvalidKeyException e) { - debug(runtime, "Certificate#verify failed: ", e); + catch (SignatureException|InvalidKeyException e) { + debug(runtime, "X509Cert#verify failed", e); return runtime.getFalse(); } } diff --git a/src/main/java/org/jruby/ext/openssl/X509Extension.java b/src/main/java/org/jruby/ext/openssl/X509Extension.java index ca3a698a..48aafbd6 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Extension.java +++ b/src/main/java/org/jruby/ext/openssl/X509Extension.java @@ -38,6 +38,7 @@ import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1IA5String; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; @@ -45,7 +46,6 @@ import org.bouncycastle.asn1.ASN1String; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.BERTags; -import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERUniversalString; import org.bouncycastle.asn1.DLSequence; @@ -61,14 +61,12 @@ import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyClass; -import org.jruby.RubyHash; import org.jruby.RubyModule; import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -78,7 +76,6 @@ import static org.jruby.ext.openssl.ASN1._ASN1; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.OpenSSL.*; -import static org.jruby.ext.openssl.StringHelper.*; /** * OpenSSL::X509::Extension @@ -87,16 +84,9 @@ public class X509Extension extends RubyObject { private static final long serialVersionUID = 6463713017143658305L; - private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Extension(runtime, klass); - } - }; - static void createX509Extension(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { // OpenSSL::X509 X509.defineClassUnder("ExtensionError", OpenSSLError, OpenSSLError.getAllocator()); - - RubyClass _Extension = X509.defineClassUnder("Extension", runtime.getObject(), X509Extension.ALLOCATOR); + RubyClass _Extension = X509.defineClassUnder("Extension", runtime.getObject(), (r, klass) -> new X509Extension(r, klass)); _Extension.defineAnnotatedMethods(X509Extension.class); X509ExtensionFactory.createX509ExtensionFactory(runtime, X509); @@ -260,6 +250,7 @@ else if ( args.length > 1 ) { return this; } + @JRubyMethod(visibility = Visibility.PRIVATE) @Override public IRubyObject initialize_copy(final IRubyObject original) { if (this == original) return this; @@ -420,30 +411,36 @@ public RubyString value(final ThreadContext context) { if ( oid.equals("2.5.29.35") ) { // authorityKeyIdentifier ASN1Encodable value = getRealValue(); - if ( value instanceof ASN1OctetString ) { + if (value instanceof ASN1OctetString) { value = ASN1.readObject( ((ASN1OctetString) value).getOctets() ); } - final ByteList val = new ByteList(72); val.append(keyid_); + final ByteList val = new ByteList(72); - if ( value instanceof ASN1Sequence ) { + if (value instanceof ASN1Sequence) { final ASN1Sequence seq = (ASN1Sequence) value; final int size = seq.size(); if ( size == 0 ) return RubyString.newEmptyString(runtime); - ASN1Primitive keyid = seq.getObjectAt(0).toASN1Primitive(); - hexBytes( keyidBytes(keyid), val ).append('\n'); - - for ( int i = 1; i < size; i++ ) { - final ASN1Encodable issuer = seq.getObjectAt(i); - // NOTE: blindly got OpenSSL tests passing (likely in-complete) : - if ( issuer instanceof ASN1TaggedObject ) { - ASN1Primitive obj = ((ASN1TaggedObject) issuer).getObject(); - switch( ((ASN1TaggedObject) issuer).getTagNo() ) { + for ( int i = 0; i < size; i++ ) { + final ASN1Encodable enc = seq.getObjectAt(i); + if (enc instanceof ASN1TaggedObject) { + ASN1Primitive obj = ((ASN1TaggedObject) enc).getBaseObject().toASN1Primitive(); + switch( ((ASN1TaggedObject) enc).getTagNo() ) { + case 0 : + ASN1Primitive keyid = obj.toASN1Primitive(); + val.append(keyid_); + hexBytes( keyidBytes(keyid), val ); + break; case 1 : - if ( obj instanceof ASN1TaggedObject ) { - formatGeneralName(GeneralName.getInstance(obj), val, true); + GeneralName name; + if (obj instanceof ASN1Sequence) { // GeneralNames -> toASN1Primitive() + GeneralName[] names = GeneralNames.getInstance(obj).getNames(); + name = names.length > 0 ? names[0] : null; + } else { + name = GeneralName.getInstance(obj); } + if (name != null) formatGeneralName(name, val, true); break; case 2 : // serial val.append(new byte[] { 's','e','r','i','a','l',':' }); @@ -451,10 +448,13 @@ public RubyString value(final ThreadContext context) { hexBytes( ((ASN1Integer) obj).getValue().toByteArray(), val); } else { - hexBytes( ((ASN1OctetString) obj ).getOctets(), val ); + hexBytes( ((ASN1OctetString) obj ).getOctets(), val ); } break; } + } else if (size == 1) { + ASN1Primitive keyid = enc.toASN1Primitive(); + hexBytes( keyidBytes(keyid), val ); } val.append('\n'); } @@ -594,7 +594,7 @@ private RubyString rawValueAsString(final ThreadContext context) throws IOExcept private static byte[] keyidBytes(ASN1Primitive keyid) throws IOException { if ( keyid instanceof ASN1TaggedObject ) { - keyid = ((ASN1TaggedObject) keyid).getObject(); + keyid = ((ASN1TaggedObject) keyid).getBaseObject().toASN1Primitive(); } if ( keyid instanceof ASN1OctetString ) { return ((ASN1OctetString) keyid).getOctets(); @@ -618,7 +618,7 @@ private static boolean formatGeneralName(final GeneralName name, final ByteList case GeneralName.uniformResourceIdentifier: if ( ! tagged ) out.append('U').append('R').append('I'). append(':'); - val = DERIA5String.getInstance(obj).getString(); + val = ASN1IA5String.getInstance(obj).getString(); out.append( ByteList.plain(val) ); break; case GeneralName.directoryName: @@ -797,7 +797,7 @@ public IRubyObject set_critical(final ThreadContext context, IRubyObject arg) { } @JRubyMethod - public IRubyObject to_der() { + public RubyString to_der() { try { return StringHelper.newString(getRuntime(), toDER()); } @@ -844,44 +844,6 @@ public IRubyObject eql_p(IRubyObject obj) { return equalImpl(getRuntime(), obj); } - // [ self.oid, self.value, self.critical? ] - @JRubyMethod - public RubyArray to_a(final ThreadContext context) { - RubyArray array = RubyArray.newArray(context.runtime, 3); - array.append(oid(context)); - array.append(value(context)); - array.append(critical_p(context)); - return array; - } - - // {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical? - @JRubyMethod - public RubyHash to_h(final ThreadContext context) { - final Ruby runtime = context.runtime; - RubyHash hash = RubyHash.newHash(runtime); - hash.op_aset(context, newStringFrozen(runtime, "oid"), oid(context)); - hash.op_aset(context, newStringFrozen(runtime, "value"), value(context)); - hash.op_aset(context, newStringFrozen(runtime, "critical"), critical_p(context)); - return hash; - } - - // "oid = critical, value" - @JRubyMethod - public RubyString to_s(final ThreadContext context) { - final Ruby runtime = context.runtime; - final RubyString str = RubyString.newString(runtime, oidSym(runtime)); - str.getByteList().append(' ').append('=').append(' '); - if ( isRealCritical() ) str.getByteList().append(critical__); - // self.value.gsub(/\n/, ", ") - final RubyString value = value(context); - value.callMethod(context, "gsub!", new IRubyObject[] { - RubyString.newStringShared(runtime, StringHelper.NEW_LINE), - RubyString.newStringShared(runtime, StringHelper.COMMA_SPACE) - }); - str.getByteList().append(value.getByteList()); - return str; - } - @Override public X509Extension clone() { try { diff --git a/src/main/java/org/jruby/ext/openssl/X509ExtensionFactory.java b/src/main/java/org/jruby/ext/openssl/X509ExtensionFactory.java index 26b0ccdd..2912e575 100644 --- a/src/main/java/org/jruby/ext/openssl/X509ExtensionFactory.java +++ b/src/main/java/org/jruby/ext/openssl/X509ExtensionFactory.java @@ -31,6 +31,7 @@ import java.math.BigInteger; import java.security.GeneralSecurityException; +import java.security.MessageDigest; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.x500.X500Name; @@ -46,9 +47,9 @@ import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -65,15 +66,9 @@ public class X509ExtensionFactory extends RubyObject { private static final long serialVersionUID = 3180447029639456500L; - private static ObjectAllocator ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509ExtensionFactory(runtime, klass); - } - }; - static void createX509ExtensionFactory(final Ruby runtime, final RubyModule _X509) { // OpenSSL::X509 final RubyClass _ExtensionFactory = _X509.defineClassUnder("ExtensionFactory", - runtime.getObject(), X509ExtensionFactory.ALLOCATOR); + runtime.getObject(), (r, klass) -> new X509ExtensionFactory(r, klass)); _ExtensionFactory.defineAnnotatedMethods(X509ExtensionFactory.class); } @@ -401,12 +396,16 @@ private static DLSequence parseBasicConstrains(final String valuex) { private ASN1Sequence parseAuthorityKeyIdentifier(final ThreadContext context, final String valuex) { final ASN1EncodableVector vec = new ASN1EncodableVector(); - for ( String value : valuex.split(",") ) { // e.g. "keyid:always,issuer:always" - if ( value.startsWith("keyid:") ) { // keyid:always - ASN1Encodable publicKeyIdentifier = new DEROctetString(publicKeyIdentifier(context)); + final String[] values = valuex.split(","); // e.g. "keyid:always,issuer:always" + for ( int i = 0; i new X509Name(r, klass)); X509.defineClassUnder("NameError", OpenSSLError, OpenSSLError.getAllocator()); _Name.defineAnnotatedMethods(X509Name.class); @@ -166,16 +172,17 @@ static RubyClass _Name(final Ruby runtime) { public X509Name(Ruby runtime, RubyClass type) { super(runtime,type); - oids = new ArrayList(); - values = new ArrayList(); - types = new ArrayList(); + oids = new ArrayList<>(4); + values = new ArrayList<>(4); + types = new ArrayList<>(4); } private final List oids; private final List values; // - private final List types; + private final List types; private transient X500Name name; + private transient X500Name canonicalName; private void fromASN1Sequence(final byte[] encoded) { try { @@ -242,30 +249,70 @@ private void addValue(final ASN1Encodable value) { @SuppressWarnings("unchecked") private void addType(final Ruby runtime, final ASN1Encodable value) { this.name = null; // NOTE: each fromX factory calls this ... + this.canonicalName = null; final Integer type = ASN1.typeId(value); - if ( type == null ) { + if (type == null) { warn(runtime.getCurrentContext(), this + " addType() could not resolve type for: " + value + " (" + (value == null ? "" : value.getClass().getName()) + ")"); - ((List) this.types).add( runtime.getNil() ); - } - else { - this.types.add( runtime.newFixnum( type.intValue() ) ); } + this.types.add(type); } - private void addEntry(ASN1ObjectIdentifier oid, RubyString value, RubyInteger type) - throws IOException { + /** + * @param oid + * @param value + * @param type expected to be legit at this point + * @throws RuntimeException + */ + private void addEntry(ASN1ObjectIdentifier oid, RubyString value, final int type) throws RuntimeException { this.name = null; + this.canonicalName = null; + this.values.add(NAME_ENTRY_CONVERTER.convertValueFor(oid, value, type)); this.oids.add(oid); - final ASN1Encodable convertedValue = getNameEntryConverted(). - getConvertedValue(oid, value.toString() - ); - this.values.add( convertedValue ); this.types.add(type); } - private static X509NameEntryConverter getNameEntryConverted() { - return new X509DefaultEntryConverter(); + private static final X509NameEntryConverterImpl NAME_ENTRY_CONVERTER = new X509NameEntryConverterImpl(); + + private static class X509NameEntryConverterImpl extends X509DefaultEntryConverter { + + ASN1Primitive convertValueFor(final ASN1ObjectIdentifier oid, final RubyString value, final int type) { + switch (type) { + case ASN1.BIT_STRING: + return new DERBitString(value.getBytes()); + case ASN1.OCTET_STRING: + return new DEROctetString(value.getBytes()); + case ASN1.UTF8STRING: + return new DERUTF8String(value.asJavaString()); + case ASN1.NUMERICSTRING: + return new DERNumericString(value.asJavaString()); // validate? + case ASN1.PRINTABLESTRING: + return new DERPrintableString(value.asJavaString()); + case ASN1.T61STRING: + return new DERT61String(value.asJavaString()); + case ASN1.VIDEOTEXSTRING: + return new DERVideotexString(value.getBytes()); + case ASN1.IA5STRING: + return new DERIA5String(value.asJavaString()); + case ASN1.GENERALIZEDTIME: + return new DERGeneralizedTime(value.asJavaString()); + case ASN1.UTCTIME: + return new DERUTCTime(value.asJavaString()); + case ASN1.GRAPHICSTRING: + return new DERGraphicString(value.getBytes()); + //case ASN1.ISO64STRING: + //return new DERVisibleString(value.asJavaString()); + case ASN1.GENERALSTRING: + return new DERGeneralString(value.asJavaString()); + case ASN1.UNIVERSALSTRING: + return new DERUniversalString(value.getBytes()); + case ASN1.BMPSTRING: + return new DERBMPString(value.asJavaString()); + } + + return super.getConvertedValue(oid, value.toString()); + } + } @Override @@ -286,9 +333,7 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject dn, IRuby if ( dn instanceof RubyArray ) { RubyArray ary = (RubyArray) dn; - final RubyClass _Name = _Name(runtime); - - if ( template.isNil() ) template = _Name.getConstant("OBJECT_TYPE_TEMPLATE"); + if (template.isNil()) template = _Name(runtime).getConstant("OBJECT_TYPE_TEMPLATE"); for (int i = 0; i < ary.size(); i++) { IRubyObject obj = ary.eltOk(i); @@ -299,15 +344,14 @@ public IRubyObject initialize(final ThreadContext context, IRubyObject dn, IRuby RubyArray arr = (RubyArray)obj; - IRubyObject entry0, entry1, entry2; - entry0 = arr.size() > 0 ? arr.eltOk(0) : context.nil; - entry1 = arr.size() > 1 ? arr.eltOk(1) : context.nil; - entry2 = arr.size() > 2 ? arr.eltOk(2) : context.nil; + IRubyObject name, value, type; + name = arr.size() > 0 ? arr.eltOk(0) : context.nil; + value = arr.size() > 1 ? arr.eltOk(1) : context.nil; + type = arr.size() > 2 ? arr.eltOk(2) : context.nil; - if (entry2.isNil()) entry2 = template.callMethod(context, "[]", entry0); - if (entry2.isNil()) entry2 = _Name.getConstant("DEFAULT_OBJECT_TYPE"); + if (type.isNil()) type = getDefaultType(context, name, template); - add_entry(context, entry0, entry1, entry2); + add_entry(context, name, value, type); } } else { @@ -350,7 +394,7 @@ else if ( obj instanceof ASN1Set ) { @JRubyMethod public IRubyObject add_entry(ThreadContext context, IRubyObject oid, IRubyObject value) { - return add_entry(context, oid, value, null); + return add_entry(context, oid, value, context.nil); } @JRubyMethod @@ -360,7 +404,7 @@ public IRubyObject add_entry(final ThreadContext context, final RubyString oidStr = oid.asString(); - if ( type == null || type.isNil() ) type = getDefaultType(context, oidStr); + if ( type.isNil() ) type = getDefaultType(context, oidStr); final ASN1ObjectIdentifier objectId; try { @@ -372,52 +416,63 @@ public IRubyObject add_entry(final ThreadContext context, // NOTE: won't reach here : if ( objectId == null ) throw newNameError(runtime, "invalid field name"); + final int typeInt = type.convertToInteger().getIntValue(); + if (ASN1.typeClassSafe(typeInt) == null) { + throw newNameError(runtime, "invalid type: " + typeInt); + } + try { - addEntry(objectId, value.asString(), (RubyInteger) type); + addEntry(objectId, value.asString(), typeInt); } - catch (IOException e) { - throw newNameError(runtime, "invalid value", e); + catch (RuntimeException e) { + debugStackTrace(runtime, e); + String msg = e.getMessage(); // X509DefaultEntryConverted: "can't recode value for oid " + oid.getId() + throw newNameError(runtime, msg == null ? "invalid value" : msg, e); } return this; } - private static IRubyObject getDefaultType(final ThreadContext context, final RubyString oid) { - IRubyObject template = _Name(context.runtime).getConstant("OBJECT_TYPE_TEMPLATE"); - if ( template instanceof RubyHash ) { - return ((RubyHash) template).op_aref(context, oid); - } - return template.callMethod(context, "[]", oid); + private IRubyObject getDefaultType(final ThreadContext context, final RubyString oid) { + return getDefaultType(context, oid, _Name(context.runtime).getConstant("OBJECT_TYPE_TEMPLATE")); + } + + private IRubyObject getDefaultType(final ThreadContext context, final IRubyObject oid, final IRubyObject template) { + final IRubyObject type = template instanceof RubyHash ? + ((RubyHash) template).op_aref(context, oid) : template.callMethod(context, "[]", oid); + return type.isNil() ? _Name(context.runtime).getConstant("DEFAULT_OBJECT_TYPE") : type; } @SuppressWarnings("unchecked") @JRubyMethod(name = "to_s", rest = true) public IRubyObject to_s(IRubyObject[] args) { - final Ruby runtime = getRuntime(); - int flag = 0; if ( args.length > 0 && ! args[0].isNil() ) { flag = RubyNumeric.fix2int( args[0] ); } - - /* Should follow parameters like this: - if 0 (COMPAT): - irb(main):025:0> x.to_s(OpenSSL::X509::Name::COMPAT) - => "CN=ola.bini, O=sweden/streetAddress=sweden, O=sweden/2.5.4.43343=sweden" - irb(main):026:0> x.to_s(OpenSSL::X509::Name::ONELINE) - => "CN = ola.bini, O = sweden, streetAddress = sweden, O = sweden, 2.5.4.43343 = sweden" - irb(main):027:0> x.to_s(OpenSSL::X509::Name::MULTILINE) - => "commonName = ola.bini\norganizationName = sweden\nstreetAddress = sweden\norganizationName = sweden\n2.5.4.43343 = sweden" - irb(main):028:0> x.to_s(OpenSSL::X509::Name::RFC2253) - => "2.5.4.43343=#0C0673776564656E,O=sweden,streetAddress=sweden,O=sweden,CN=ola.bini" - else - => /CN=ola.bini/O=sweden/streetAddress=sweden/O=sweden/2.5.4.43343=sweden - */ - + final Ruby runtime = getRuntime(); + // NOTE: historically we haven't screwed this up as ASCII-8BIT as C-OpenSSL does + return RubyString.newString(runtime, toFormat(runtime, flag)); + } + + /* Should follow parameters like this: + if 0 (COMPAT): + irb(main):025:0> x.to_s(OpenSSL::X509::Name::COMPAT) + => "CN=ola.bini, O=sweden/streetAddress=sweden, O=sweden/2.5.4.43343=sweden" + irb(main):026:0> x.to_s(OpenSSL::X509::Name::ONELINE) + => "CN = ola.bini, O = sweden, streetAddress = sweden, O = sweden, 2.5.4.43343 = sweden" + irb(main):027:0> x.to_s(OpenSSL::X509::Name::MULTILINE) + => "commonName = ola.bini\norganizationName = sweden\nstreetAddress = sweden\norganizationName = sweden\n2.5.4.43343 = sweden" + irb(main):028:0> x.to_s(OpenSSL::X509::Name::RFC2253) + => "2.5.4.43343=#0C0673776564656E,O=sweden,streetAddress=sweden,O=sweden,CN=ola.bini" + else + => /CN=ola.bini/O=sweden/streetAddress=sweden/O=sweden/2.5.4.43343=sweden + */ + private StringBuilder toFormat(final Ruby runtime, final int format) { final Iterator oidsIter; final Iterator valuesIter; - if ( flag == RFC2253 ) { - ArrayList reverseOids = new ArrayList(oids); - ArrayList reverseValues = new ArrayList(values); + if ( format == RFC2253 ) { + ArrayList reverseOids = new ArrayList<>(oids); + ArrayList reverseValues = new ArrayList<>(values); Collections.reverse(reverseOids); Collections.reverse(reverseValues); oidsIter = reverseOids.iterator(); @@ -428,15 +483,16 @@ public IRubyObject to_s(IRubyObject[] args) { } final StringBuilder str = new StringBuilder(48); String sep = ""; - while( oidsIter.hasNext() ) { + while (oidsIter.hasNext()) { final ASN1ObjectIdentifier oid = oidsIter.next(); String oName = name(runtime, oid); if ( oName == null ) oName = oid.toString(); - final Object value = valuesIter.next(); + final Object value = valuesIter.next(); // ASN1String impl (getString() -> toString()) - switch(flag) { + switch (format) { case RFC2253: - str.append(sep).append(oName).append('=').append(value); + str.append(sep).append(oName).append('='); + appendValueRFC2253(str, value); sep = ","; break; case ONELINE: @@ -458,14 +514,39 @@ public IRubyObject to_s(IRubyObject[] args) { } } - return runtime.newString( str.toString() ); + return str; + } + + private static void appendValueRFC2253(final StringBuilder str, final Object value) { + final String val = value.toString(); + for (int i = 0; i < val.length(); i++) { + char c = val.charAt(i); + switch (c) { + case ',' : + case '+' : + case '"' : + case '<' : + case '>' : + case ';' : + case '\\' : + str.append('\\').append(c); + break; + default : + str.append(c); + } + } + } + + @JRubyMethod + public IRubyObject to_utf8(ThreadContext context) { + return StringHelper.newUTF8String(context.runtime, toFormat(context.runtime, RFC2253)); } @Override @SuppressWarnings("unchecked") @JRubyMethod public IRubyObject inspect() { - return ObjectSupport.inspect(this, Collections.EMPTY_LIST); + return ObjectSupport.inspect(this, toFormat(getRuntime(), RFC2253)); } @Override @@ -474,17 +555,18 @@ public RubyArray to_a() { final Ruby runtime = getRuntime(); final RubyArray entries = runtime.newArray( oids.size() ); final Iterator oidsIter = oids.iterator(); - @SuppressWarnings("unchecked") - final Iterator valuesIter = (Iterator) values.iterator(); - final Iterator typesIter = types.iterator(); + final Iterator valuesIter = values.iterator(); + final Iterator typesIter = types.iterator(); while ( oidsIter.hasNext() ) { final ASN1ObjectIdentifier oid = oidsIter.next(); String oName = name(runtime, oid); if ( oName == null ) oName = oid.toString(); final String value = valuesIter.next().toString(); - final IRubyObject type = typesIter.next(); + final Integer type = typesIter.next(); final IRubyObject[] entry = new IRubyObject[] { - runtime.newString(oName), runtime.newString(value), type + StringHelper.newUTF8String(runtime, oName), + StringHelper.newUTF8String(runtime, value), + type == null ? runtime.getNil() : runtime.newFixnum(type) }; entries.append( runtime.newArrayNoCopy(entry) ); } @@ -495,16 +577,6 @@ private static String name(final Ruby runtime, final ASN1ObjectIdentifier oid) { return ASN1.oid2name(runtime, oid, true); } - @Deprecated - @SuppressWarnings("unchecked") - org.bouncycastle.asn1.x509.X509Name getRealName() { - final java.util.Vector strValues = new java.util.Vector(); - for ( ASN1Encodable value : values ) strValues.add( value.toString() ); - return new org.bouncycastle.asn1.x509.X509Name( - new java.util.Vector(oids), strValues - ); - } - final X500Name getX500Name() { if ( name != null ) return name; @@ -515,6 +587,50 @@ final X500Name getX500Name() { return name = builder.build(); } + final X500Name getCanonicalX500Name() { + if ( canonicalName != null ) return canonicalName; + + final X500NameBuilder builder = new X500NameBuilder( BCStyle.INSTANCE ); + for ( int i = 0; i < oids.size(); i++ ) { + ASN1Encodable value = values.get(i); + value = canonicalize(value); + builder.addRDN( oids.get(i), value ); + } + return canonicalName = builder.build(); + } + + private ASN1Encodable canonicalize(ASN1Encodable value) { + if (value instanceof ASN1String) { + ASN1String string = (ASN1String) value; + return new DERUTF8String(canonicalize(string.getString())); + } + return value; + } + + private String canonicalize(String string) { + //asn1_string_canon (trim, to lower case, collapse multiple spaces) + string = string.trim(); + if (string.length() == 0) { + return string; + } + + StringBuilder out = new StringBuilder(); + int i = 0; + while (i < string.length()) { + char c = string.charAt(i); + if (Character.isWhitespace(c)){ + out.append(' '); + while (i < string.length() && Character.isWhitespace(string.charAt(i))) { + i++; + } + } else { + out.append(Character.toLowerCase(c)); + i++; + } + } + return out.toString(); + } + @JRubyMethod(name = { "cmp", "<=>" }) public RubyFixnum cmp(IRubyObject other) { if ( equals(other) ) { @@ -523,8 +639,8 @@ public RubyFixnum cmp(IRubyObject other) { // TODO: do we really need cmp - if so what order huh? if ( other instanceof X509Name ) { final X509Name that = (X509Name) other; - final X500Name thisName = this.getX500Name(); - final X500Name thatName = that.getX500Name(); + final X500Name thisName = this.getCanonicalX500Name(); + final X500Name thatName = that.getCanonicalX500Name(); int cmp = thisName.toString().compareTo( thatName.toString() ); return RubyFixnum.newFixnum( getRuntime(), cmp ); } @@ -536,8 +652,8 @@ public boolean equals(Object other) { if ( this == other ) return true; if ( other instanceof X509Name ) { final X509Name that = (X509Name) other; - final X500Name thisName = this.getX500Name(); - final X500Name thatName = that.getX500Name(); + final X500Name thisName = this.getCanonicalX500Name(); + final X500Name thatName = that.getCanonicalX500Name(); return thisName.equals(thatName); } return false; @@ -546,7 +662,7 @@ public boolean equals(Object other) { @Override public int hashCode() { try { - return Name.hash( getX500Name() ); + return (int) Name.hash( getCanonicalX500Name() ); } catch (IOException e) { debugStackTrace(getRuntime(), e); return 0; @@ -554,7 +670,6 @@ public int hashCode() { catch (RuntimeException e) { debugStackTrace(getRuntime(), e); return 0; } - // return 41 * this.oids.hashCode(); } @JRubyMethod(name = "eql?") @@ -571,7 +686,32 @@ public IRubyObject eql_p(final IRubyObject obj) { @Override @JRubyMethod public RubyFixnum hash() { - return getRuntime().newFixnum( hashCode() ); + long hash; + try { + hash = Name.hash( getCanonicalX500Name() ); + } + catch (IOException e) { + debugStackTrace(getRuntime(), e); hash = 0; + } + catch (RuntimeException e) { + debugStackTrace(getRuntime(), e); hash = 0; + } + return getRuntime().newFixnum(hash); + } + + @JRubyMethod + public RubyFixnum hash_old() { + long hash; + try { + hash = Name.hashOld( getX500Name() ); + } + catch (IOException e) { + debugStackTrace(getRuntime(), e); hash = 0; + } + catch (RuntimeException e) { + debugStackTrace(getRuntime(), e); hash = 0; + } + return getRuntime().newFixnum( hash ); } @JRubyMethod diff --git a/src/main/java/org/jruby/ext/openssl/X509Request.java b/src/main/java/org/jruby/ext/openssl/X509Request.java index e390b53a..7947f38f 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Request.java +++ b/src/main/java/org/jruby/ext/openssl/X509Request.java @@ -42,9 +42,9 @@ import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -54,14 +54,15 @@ import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; import org.jruby.exceptions.RaiseException; +import org.jruby.ext.openssl.impl.ASN1Registry; import org.jruby.ext.openssl.x509store.PEMInputOutput; import org.jruby.runtime.Arity; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.Visibility; import org.jruby.ext.openssl.impl.PKCS10Request; + import static org.jruby.ext.openssl.OpenSSL.*; import static org.jruby.ext.openssl.PKey._PKey; import static org.jruby.ext.openssl.X509._X509; @@ -73,14 +74,8 @@ public class X509Request extends RubyObject { private static final long serialVersionUID = -2886532636278901502L; - private static ObjectAllocator REQUEST_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Request(runtime, klass); - } - }; - public static void createRequest(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass _Request = X509.defineClassUnder("Request", runtime.getObject(), REQUEST_ALLOCATOR); + RubyClass _Request = X509.defineClassUnder("Request", runtime.getObject(), (r, klass) -> new X509Request(r, klass)); X509.defineClassUnder("RequestError", OpenSSLError, OpenSSLError.getAllocator()); _Request.defineAnnotatedMethods(X509Request.class); } @@ -98,7 +93,7 @@ static RubyClass _RequestError(final Ruby runtime) { public X509Request(Ruby runtime, RubyClass type) { super(runtime, type); - attributes = new ArrayList(4); + attributes = new ArrayList<>(4); } @JRubyMethod(name = "initialize", rest = true, visibility = Visibility.PRIVATE) @@ -115,25 +110,14 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a throw newRequestError(runtime, "invalid certificate request data", e); } - final String algorithm; final byte[] encoded; + final PublicKey publicKey; try { - final PublicKey pkey = request.generatePublicKey(); - algorithm = pkey.getAlgorithm(); - encoded = pkey.getEncoded(); - } - catch (IOException e) { throw newRequestError(runtime, e); } - catch (GeneralSecurityException e) { throw newRequestError(runtime, e); } - - final RubyString enc = RubyString.newString(runtime, encoded); - if ( "RSA".equalsIgnoreCase(algorithm) ) { - this.public_key = newPKeyImplInstance(context, "RSA", enc); + publicKey = request.generatePublicKey(); } - else if ( "DSA".equalsIgnoreCase(algorithm) ) { - this.public_key = newPKeyImplInstance(context, "DSA", enc); - } - else { - throw runtime.newNotImplementedError("public key algorithm: " + algorithm); + catch (IOException|GeneralSecurityException e) { + throw newRequestError(runtime, e); } + this.public_key = PKey.newInstance(runtime, publicKey); this.subject = newName( context, request.getSubject() ); @@ -154,11 +138,6 @@ else if ( "DSA".equalsIgnoreCase(algorithm) ) { return this; } - private static PKey newPKeyImplInstance(final ThreadContext context, - final String className, final RubyString encoded) { // OpenSSL::PKey::RSA.new(encoded) - return (PKey) _PKey(context.runtime).getClass(className).callMethod(context, "new", encoded); - } - private static X509Attribute newAttribute(final ThreadContext context, final ASN1ObjectIdentifier type, final ASN1Set values) throws IOException { return X509Attribute.newAttribute(context.runtime, type, values); @@ -266,7 +245,7 @@ public IRubyObject version() { return getRuntime().newFixnum( certVersion.intValue() ); } } - return version == null ? getRuntime().newFixnum(0) : version; + return version == null ? getRuntime().newFixnum(-1) : version; } @JRubyMethod(name="version=") @@ -293,8 +272,10 @@ public IRubyObject set_subject(final IRubyObject val) { @JRubyMethod public IRubyObject signature_algorithm(final ThreadContext context) { - warn(context, "WARNING: unimplemented method called: OpenSSL::X509::Request#signature_algorithm"); - return context.runtime.getNil(); + AlgorithmIdentifier signatureAlgId = request == null ? null : request.getSignatureAlgorithm(); + if (signatureAlgId == null) return context.runtime.newString("NULL"); + final String name = ASN1Registry.o2a(signatureAlgId.getAlgorithm()); + return context.runtime.newString(name == null ? "" : name); } @JRubyMethod @@ -316,7 +297,6 @@ public IRubyObject set_public_key(final IRubyObject pkey) { @JRubyMethod public IRubyObject sign(final ThreadContext context, final IRubyObject key, final IRubyObject digest) { - // PublicKey publicKey = public_key.getPublicKey(); PrivateKey privateKey = ((PKey) key).getPrivateKey(); final Ruby runtime = context.runtime; @@ -324,51 +304,53 @@ public IRubyObject sign(final ThreadContext context, final String digAlg = ((Digest) digest).getShortAlgorithm(); try { - request = null; getRequest().sign( privateKey, digAlg ); + request = null; + getRequest().sign( privateKey, digAlg ); } - catch (GeneralSecurityException e) { - debugStackTrace(runtime, e); + catch (InvalidKeyException e) { + debug(runtime, "X509Request#sign invalid key:", e); + throw newRequestError(runtime, e); + } + catch (Exception e) { + debugStackTrace(runtime, "X509Request#sign", e); throw newRequestError(runtime, e); } - //catch (IOException e) { - // debugStackTrace(runtime, e); - // throw newRequestError(runtime, e); - //} return this; } private List newAttributesImpl(final ThreadContext context) { - ArrayList attrs = new ArrayList(attributes.size()); + ArrayList attrs = new ArrayList<>(attributes.size()); for ( X509Attribute attribute : attributes ) { attrs.add( newAttributeImpl(context, attribute) ); } return attrs; } - private Attribute newAttributeImpl(final ThreadContext context, - final X509Attribute attribute) { + private static Attribute newAttributeImpl(final ThreadContext context, final X509Attribute attribute) { return Attribute.getInstance( attribute.toASN1( context ) ); } @JRubyMethod public IRubyObject verify(final ThreadContext context, IRubyObject key) { - final Ruby runtime = context.runtime; final PublicKey publicKey; + final Ruby runtime = context.runtime; try { - publicKey = ( (PKey) key.callMethod(context, "public_key") ).getPublicKey(); - return runtime.newBoolean( getRequest().verify(publicKey) ); + if (!(key instanceof PKey)) { // due PKeyEC + key = key.callMethod(context, "public_key"); + if (!(key instanceof PKey)) { + throw context.runtime.newTypeError(key, _PKey(runtime)); + } + } + boolean signatureValid = getRequest().verify( ((PKey) key).getPublicKey() ); + return runtime.newBoolean(signatureValid); } catch (InvalidKeyException e) { - debug(runtime, "X509::Request.verify invalid key", e); + debug(runtime, "X509Request#verify invalid key:", e); throw newRequestError(runtime, "invalid key supplied", e); } - //catch (IOException e) { - // debug(runtime, "X509::Request.verify failed", e); - // return runtime.getFalse(); - //} - //catch (RuntimeException e) { - // debug(runtime, "X509::Request.verify failed", e); - // return runtime.getFalse(); - //} + catch (RuntimeException e) { + debugStackTrace(runtime, "X509Request#verify", e); + return context.nil; + } } @JRubyMethod @@ -378,7 +360,7 @@ public IRubyObject attributes() { return getRuntime().newArray(attributes); } - @JRubyMethod(name="attributes=") + @JRubyMethod(name = "attributes=") public IRubyObject set_attributes(final ThreadContext context,final IRubyObject attributes) { this.attributes.clear(); final RubyArray attrs = (RubyArray) attributes; diff --git a/src/main/java/org/jruby/ext/openssl/X509Revoked.java b/src/main/java/org/jruby/ext/openssl/X509Revoked.java index 28778358..baf58d26 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Revoked.java +++ b/src/main/java/org/jruby/ext/openssl/X509Revoked.java @@ -42,7 +42,6 @@ import org.jruby.RubyObject; import org.jruby.RubyTime; import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.Visibility; @@ -58,14 +57,8 @@ public class X509Revoked extends RubyObject { private static final long serialVersionUID = -6238325248555061878L; - private static ObjectAllocator X509REVOKED_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Revoked(runtime, klass); - } - }; - static void createX509Revoked(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass Revoked = X509.defineClassUnder("Revoked", runtime.getObject(), X509REVOKED_ALLOCATOR); + RubyClass Revoked = X509.defineClassUnder("Revoked", runtime.getObject(), (r, klass) -> new X509Revoked(r, klass)); X509.defineClassUnder("RevokedError", OpenSSLError, OpenSSLError.getAllocator()); Revoked.defineAnnotatedMethods(X509Revoked.class); } diff --git a/src/main/java/org/jruby/ext/openssl/X509Store.java b/src/main/java/org/jruby/ext/openssl/X509Store.java index abe97f65..ff085cca 100644 --- a/src/main/java/org/jruby/ext/openssl/X509Store.java +++ b/src/main/java/org/jruby/ext/openssl/X509Store.java @@ -30,6 +30,7 @@ import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; import static org.jruby.ext.openssl.OpenSSL.warn; import static org.jruby.ext.openssl.X509._X509; +import static org.jruby.ext.openssl.x509store.StoreContext.ossl_ssl_ex_vcb_idx; import org.jruby.Ruby; import org.jruby.RubyClass; @@ -46,7 +47,6 @@ import org.jruby.ext.openssl.x509store.X509Utils; import org.jruby.runtime.Arity; import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; @@ -58,14 +58,8 @@ public class X509Store extends RubyObject { private static final long serialVersionUID = -2969708892287379665L; - private static ObjectAllocator X509STORE_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509Store(runtime, klass); - } - }; - static void createX509Store(final Ruby runtime, final RubyModule X509, final RubyClass OpenSSLError) { - RubyClass Store = X509.defineClassUnder("Store", runtime.getObject(), X509STORE_ALLOCATOR); + RubyClass Store = X509.defineClassUnder("Store", runtime.getObject(), (r, klass) -> new X509Store(r, klass)); X509.defineClassUnder("StoreError", OpenSSLError, OpenSSLError.getAllocator()); final ThreadContext context = runtime.getCurrentContext(); @@ -119,13 +113,18 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a } @JRubyMethod - public IRubyObject verify_callback() { + public IRubyObject verify_callback(ThreadContext context) { + IRubyObject verify_callback = verify_callback_internal(); + return verify_callback == null ? context.nil : verify_callback; + } + + final IRubyObject verify_callback_internal() { return this.getInstanceVariable("@verify_callback"); } @JRubyMethod(name = "verify_callback=") public IRubyObject set_verify_callback(final IRubyObject callback) { - store.setExtraData(1, callback); + store.setExtraData(ossl_ssl_ex_vcb_idx, callback.isNil() ? null : callback); this.setInstanceVariable("@verify_callback", callback); return callback; } @@ -235,32 +234,34 @@ public IRubyObject verify(final ThreadContext context, final IRubyObject[] args, private static Store.VerifyCallbackFunction verifyCallback = new Store.VerifyCallbackFunction() { public int call(final StoreContext context, final Integer outcome) { - int ok = outcome.intValue(); - IRubyObject proc = (IRubyObject) context.getExtraData(1); + int preverify_ok = outcome.intValue(); + + IRubyObject proc = (IRubyObject) context.getExtraData(ossl_ssl_ex_vcb_idx); if (proc == null) { - proc = (IRubyObject) context.getStore().getExtraData(0); + proc = (IRubyObject) context.getStore().getExtraData(ossl_ssl_ex_vcb_idx); + + if (proc == null) return preverify_ok; } - if ( proc == null ) return ok; if ( ! proc.isNil() ) { final Ruby runtime = proc.getRuntime(); X509StoreContext store_context = X509StoreContext.newStoreContext(runtime, context); IRubyObject ret = proc.callMethod(runtime.getCurrentContext(), "call", - new IRubyObject[] { runtime.newBoolean(ok != 0), store_context } + new IRubyObject[] { runtime.newBoolean(preverify_ok != 0), store_context } ); if (ret.isTrue()) { context.setError(X509Utils.V_OK); - ok = 1; + preverify_ok = 1; } else { if (context.getError() == X509Utils.V_OK) { context.setError(X509Utils.V_ERR_CERT_REJECTED); } - ok = 0; + preverify_ok = 0; } } - return ok; + return preverify_ok; } }; diff --git a/src/main/java/org/jruby/ext/openssl/X509StoreContext.java b/src/main/java/org/jruby/ext/openssl/X509StoreContext.java index 92992143..61eb364b 100644 --- a/src/main/java/org/jruby/ext/openssl/X509StoreContext.java +++ b/src/main/java/org/jruby/ext/openssl/X509StoreContext.java @@ -50,10 +50,10 @@ import org.jruby.ext.openssl.x509store.StoreContext; import static org.jruby.ext.openssl.OpenSSL.debugStackTrace; -import static org.jruby.ext.openssl.OpenSSL.warn; import static org.jruby.ext.openssl.X509._X509; import static org.jruby.ext.openssl.X509CRL._CRL; import static org.jruby.ext.openssl.X509Cert._Certificate; +import static org.jruby.ext.openssl.x509store.StoreContext.ossl_ssl_ex_vcb_idx; import static org.jruby.ext.openssl.x509store.X509Utils.verifyCertificateErrorString; /** @@ -62,14 +62,9 @@ public class X509StoreContext extends RubyObject { private static final long serialVersionUID = -4165247923898746888L; - private static ObjectAllocator X509STORECTX_ALLOCATOR = new ObjectAllocator() { - public IRubyObject allocate(Ruby runtime, RubyClass klass) { - return new X509StoreContext(runtime, klass); - } - }; - public static void createX509StoreContext(final Ruby runtime, final RubyModule X509) { - RubyClass StoreContext = X509.defineClassUnder("StoreContext", runtime.getObject(), X509STORECTX_ALLOCATOR); + RubyClass StoreContext = X509.defineClassUnder("StoreContext", runtime.getObject(), + (r, klass) -> new X509StoreContext(r, klass)); StoreContext.defineAnnotatedMethods(X509StoreContext.class); StoreContext.undefineMethod("dup"); } @@ -144,20 +139,24 @@ public IRubyObject initialize(final ThreadContext context, final IRubyObject[] a this.storeContext = new StoreContext(store.getStore()); if ( storeContext.init(_cert, _chain) != 1 ) { - throw newStoreError(context.runtime, null); + throw newStoreError(context.runtime, (String) null); } IRubyObject time = store.getInstanceVariables().getInstanceVariable("@time"); if ( ! time.isNil() ) set_time(time); - this.setInstanceVariable("@verify_callback", store.verify_callback()); - this.setInstanceVariable("@cert", cert); + IRubyObject verify_callback = store.verify_callback_internal(); + if (verify_callback != null) this.setInstanceVariable("@verify_callback", verify_callback); + if (cert != null) this.setInstanceVariable("@cert", cert); return this; } @JRubyMethod public IRubyObject verify(final ThreadContext context) { final Ruby runtime = context.runtime; - storeContext.setExtraData(1, getInstanceVariable("@verify_callback")); + IRubyObject verify_callback = this.getInstanceVariable("@verify_callback"); + if (verify_callback != null && !verify_callback.isNil()) { + storeContext.setExtraData(ossl_ssl_ex_vcb_idx, verify_callback); + } try { final int result = storeContext.verifyCertificate(); return result != 0 ? runtime.getTrue() : runtime.getFalse(); @@ -165,7 +164,7 @@ public IRubyObject verify(final ThreadContext context) { catch (Exception e) { debugStackTrace(runtime, e); // TODO: define suitable exception for jopenssl and catch it. - throw newStoreError(runtime, e.getMessage()); + throw newStoreError(runtime, e); } } @@ -184,7 +183,7 @@ public IRubyObject chain(final ThreadContext context) { } } catch (CertificateEncodingException e) { - throw newStoreError(runtime, e.getMessage()); + throw newStoreError(runtime, e); } return result; } @@ -219,7 +218,7 @@ public IRubyObject current_cert(final ThreadContext context) { return X509Cert.wrap(context, x509.getEncoded()); } catch (CertificateEncodingException e) { - throw newStoreError(context.runtime, e.getMessage()); + throw newStoreError(context.runtime, e); } } @@ -232,7 +231,7 @@ public IRubyObject current_crl(final ThreadContext context) { return _CRL.newInstance(context, StringHelper.newString(runtime, crl.getEncoded()), Block.NULL_BLOCK); } catch (CRLException e) { - throw newStoreError(runtime, e.getMessage()); + throw newStoreError(runtime, e); } } @@ -241,14 +240,13 @@ public IRubyObject cleanup(final ThreadContext context) { try { storeContext.cleanup(); } - catch (RuntimeException e) { + catch (RaiseException e) { throw e; } catch (Exception e) { - debugStackTrace(context.runtime, e); - throw newStoreError(context.runtime, e.getMessage()); + throw newStoreError(context.runtime, e); } - return context.runtime.getNil(); + return context.nil; } @JRubyMethod(name = "flags=") @@ -279,4 +277,10 @@ private static RaiseException newStoreError(Ruby runtime, String message) { return Utils.newError(runtime, _X509(runtime).getClass("StoreError"), message); } + private static RaiseException newStoreError(Ruby runtime, Exception cause) { + RaiseException ex = newStoreError(runtime, cause.getMessage()); + ex.initCause(cause); + return ex; + } + }// X509StoreContext diff --git a/src/main/java/org/jruby/ext/openssl/impl/ASN1Registry.java b/src/main/java/org/jruby/ext/openssl/impl/ASN1Registry.java index 8304bfb9..b2d948d9 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/ASN1Registry.java +++ b/src/main/java/org/jruby/ext/openssl/impl/ASN1Registry.java @@ -62,20 +62,19 @@ public static String o2a(final ASN1ObjectIdentifier oid) { return o2a( oid.getId() ); } - //@Deprecated public static ASN1ObjectIdentifier sym2oid(final String name) { final String oid = SYM_TO_OID.get( name.toLowerCase() ); return oid == null ? null : new ASN1ObjectIdentifier( oid ); } - public static String name2oid(final String name) { + static String name2oid(final String name) { return SYM_TO_OID.get( name.toLowerCase() ); } public static Map getOIDLookup() { return SYM_TO_OID; } static ASN1ObjectIdentifier nid2obj(int nid) { - return new ASN1ObjectIdentifier( NID_TO_OID[ nid ] ); + return new ASN1ObjectIdentifier(nid2oid(nid)); } public static String nid2oid(int nid) { @@ -3748,7 +3747,10 @@ public static String nid2sn(int nid) { public static final String SN_gost89_cnt = "gost89-cnt"; public static final short NID_gost89_cnt = 814; - + + public static final String SN_gost89_cnt_12 = "gost89-cnt-12"; + public static final short NID_gost89_cnt_12 = 975; + public static final String SN_id_Gost28147_89_MAC = "gost-mac"; public static final String LN_id_Gost28147_89_MAC = "GOST 28147-89 MAC"; public static final short NID_id_Gost28147_89_MAC = 815; @@ -4079,7 +4081,15 @@ public static String nid2sn(int nid) { public static final String SN_aes_256_cbc_hmac_sha256 = "AES-256-CBC-HMAC-SHA256"; public static final String LN_aes_256_cbc_hmac_sha256 = "aes-256-cbc-hmac-sha256"; public static final short NID_aes_256_cbc_hmac_sha256 = 950; - + + public static final String SN_chacha20_poly1305 = "ChaCha20-Poly1305"; + public static final String LN_chacha20_poly1305 = "chacha20-poly1305"; + public static final short NID_chacha20_poly1305 = 1018; + + public static final String SN_chacha20 = "ChaCha20"; + public static final String LN_chacha20 = "chacha20"; + public static final short NID_chacha20 = 1019; + public static final String SN_dhpublicnumber = "dhpublicnumber"; public static final String LN_dhpublicnumber = "X9.42 DH"; public static final short NID_dhpublicnumber = 920; diff --git a/src/main/java/org/jruby/ext/openssl/impl/EncContent.java b/src/main/java/org/jruby/ext/openssl/impl/EncContent.java index d03593d1..fc7da603 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/EncContent.java +++ b/src/main/java/org/jruby/ext/openssl/impl/EncContent.java @@ -157,7 +157,7 @@ public static EncContent fromASN1(final ASN1Encodable content) { ec.setContentType( ASN1Registry.oid2nid(contentType) ); ec.setAlgorithm(AlgorithmIdentifier.getInstance(sequence.getObjectAt(1))); if(sequence.size() > 2 && sequence.getObjectAt(2) instanceof ASN1TaggedObject && ((ASN1TaggedObject)(sequence.getObjectAt(2))).getTagNo() == 0) { - ASN1Encodable ee = ((ASN1TaggedObject)(sequence.getObjectAt(2))).getObject(); + ASN1Encodable ee = ((ASN1TaggedObject)(sequence.getObjectAt(2))).getBaseObject().toASN1Primitive(); if ( ee instanceof ASN1Sequence && ((ASN1Sequence) ee).size() > 0 ) { ByteList combinedOctets = new ByteList(); Enumeration enm = ((ASN1Sequence)ee).getObjects(); diff --git a/src/main/java/org/jruby/ext/openssl/impl/PKCS10Request.java b/src/main/java/org/jruby/ext/openssl/impl/PKCS10Request.java index 43c1b038..1ddbb899 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/PKCS10Request.java +++ b/src/main/java/org/jruby/ext/openssl/impl/PKCS10Request.java @@ -45,12 +45,15 @@ import java.security.spec.InvalidKeySpecException; import java.util.Enumeration; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DLSequence; import org.bouncycastle.asn1.pkcs.CertificationRequestInfo; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; @@ -124,28 +127,24 @@ private void resetSignedRequest() { // sign - public PKCS10CertificationRequest sign(final PrivateKey privateKey, - final AlgorithmIdentifier signatureAlg) + public PKCS10CertificationRequest sign(final PrivateKey privateKey, final AlgorithmIdentifier signatureAlg) throws NoSuchAlgorithmException, InvalidKeyException { final ContentSigner signer = new PKCS10Signer(privateKey, signatureAlg); signedRequest = newBuilder().build(signer); // valid = true; return signedRequest; } - public PKCS10CertificationRequest sign(final PrivateKey privateKey, - final String digestAlg) + public PKCS10CertificationRequest sign(final PrivateKey privateKey, final String digestAlg) throws NoSuchAlgorithmException, InvalidKeyException { String sigAlg = digestAlg + "WITH" + getPublicKeyAlgorithm(); - return sign( privateKey, - new DefaultSignatureAlgorithmIdentifierFinder().find( sigAlg ) - ); + return sign(privateKey, new DefaultSignatureAlgorithmIdentifierFinder().find(sigAlg)); } // verify public boolean verify(final PublicKey publicKey) throws InvalidKeyException { if ( signedRequest == null ) { - if ( true ) throw new IllegalStateException("no signed request"); + assert false : "no signed request"; return false; } @@ -206,16 +205,34 @@ public void setPublicKey(final PublicKey publicKey) { resetSignedRequest(); } + /** + * @return e.g. "RSA" or "ECDSA" + */ private String getPublicKeyAlgorithm() { - //if ( publicKeyAlgorithm == null ) { - // throw new IllegalStateException("no public key info"); - //} - //return publicKeyAlgorithm; if ( publicKeyInfo == null ) { throw new IllegalStateException("no public key info"); } - AlgorithmIdentifier algId = publicKeyInfo.getAlgorithm(); - return ASN1Registry.oid2sym( algId.getAlgorithm() ); + + assert publicKeyInfo.getAlgorithm() != null : "null algorithm for public key info: " + publicKeyInfo; + final ASN1ObjectIdentifier algOID = publicKeyInfo.getAlgorithm().getAlgorithm(); + assert algOID != null; + + if (PKCSObjectIdentifiers.rsaEncryption.getId().equals(algOID.getId())) { + return "RSA"; + } + if (X9ObjectIdentifiers.id_ecPublicKey.getId().equals(algOID.getId())) { + return "ECDSA"; + } + if (X9ObjectIdentifiers.id_dsa.getId().equals(algOID.getId())) { + return "DSA"; + } + + // final String algName = new DefaultAlgorithmNameFinder().getAlgorithmName(algId); + // assert algId.getAlgorithm().getId() != algName : "could not resolve name for oid: " + algId.getAlgorithm(); + // return algName; + + assert false : "unexpected public key algorithm oid: " + algOID.getId(); + return null; } public PublicKey generatePublicKey() throws NoSuchAlgorithmException, @@ -289,6 +306,10 @@ public BigInteger getVersion() { getVersion().getValue(); } + public AlgorithmIdentifier getSignatureAlgorithm() { + if ( signedRequest == null ) return null; + return signedRequest.getSignatureAlgorithm(); + } private static class PKCS10Signer implements ContentSigner { diff --git a/src/main/java/org/jruby/ext/openssl/impl/PKCS7.java b/src/main/java/org/jruby/ext/openssl/impl/PKCS7.java index 12009f9b..1619177f 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/PKCS7.java +++ b/src/main/java/org/jruby/ext/openssl/impl/PKCS7.java @@ -67,6 +67,9 @@ import org.jruby.ext.openssl.x509store.StoreContext; import org.jruby.ext.openssl.x509store.X509AuxCertificate; import org.jruby.ext.openssl.x509store.X509Utils; +import org.jruby.runtime.builtin.IRubyObject; + +import static org.jruby.ext.openssl.x509store.StoreContext.ossl_ssl_ex_vcb_idx; /** c: PKCS7 * @@ -140,7 +143,7 @@ public static PKCS7 fromASN1(ASN1Encodable obj) throws PKCS7Exception { ASN1Encodable content = size == 1 ? null : ((ASN1Sequence) obj).getObjectAt(1); if (content != null && content instanceof ASN1TaggedObject && ((ASN1TaggedObject) content).getTagNo() == 0) { - content = ((ASN1TaggedObject) content).getObject(); + content = ((ASN1TaggedObject) content).getBaseObject().toASN1Primitive(); } p7.initiateWith(nid, content); } @@ -350,7 +353,12 @@ public void verify(Collection certs, Store store, BIO indata else if ( certContext.init(signer, null) == 0 ) { throw new PKCS7Exception(F_PKCS7_VERIFY, -1); } - certContext.setExtraData(1, store.getExtraData(1)); + + Object verify_callback = store.getExtraData(ossl_ssl_ex_vcb_idx); + if (verify_callback != null) { + certContext.setExtraData(ossl_ssl_ex_vcb_idx, verify_callback); + } + if ( (flags & NOCRL) == 0 ) { certContext.setCRLs((List) getSign().getCrl()); } diff --git a/src/main/java/org/jruby/ext/openssl/impl/PKey.java b/src/main/java/org/jruby/ext/openssl/impl/PKey.java index 3a536f24..c50726ae 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/PKey.java +++ b/src/main/java/org/jruby/ext/openssl/impl/PKey.java @@ -27,7 +27,10 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ext.openssl.impl; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.math.BigInteger; import java.security.KeyFactory; @@ -39,13 +42,10 @@ import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPrivateKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; @@ -54,21 +54,27 @@ import java.security.spec.X509EncodedKeySpec; import javax.crypto.spec.DHParameterSpec; +import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.DERNull; +import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.sec.ECPrivateKeyStructure; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x509.DSAParameter; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; -import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.jruby.ext.openssl.SecurityHelper; @@ -80,86 +86,64 @@ */ public class PKey { - public static KeyPair readPrivateKey(final byte[] input, final String type) + public enum Type { RSA, DSA, EC; } + + public static KeyPair readPrivateKey(final Type type, final byte[] input) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { - KeySpec pubSpec; KeySpec privSpec; - ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject(); - if ( type.equals("RSA") ) { - ASN1Integer mod = (ASN1Integer) seq.getObjectAt(1); - ASN1Integer pubExp = (ASN1Integer) seq.getObjectAt(2); - ASN1Integer privExp = (ASN1Integer) seq.getObjectAt(3); - ASN1Integer p1 = (ASN1Integer) seq.getObjectAt(4); - ASN1Integer p2 = (ASN1Integer) seq.getObjectAt(5); - ASN1Integer exp1 = (ASN1Integer) seq.getObjectAt(6); - ASN1Integer exp2 = (ASN1Integer) seq.getObjectAt(7); - ASN1Integer crtCoef = (ASN1Integer) seq.getObjectAt(8); - pubSpec = new RSAPublicKeySpec(mod.getValue(), pubExp.getValue()); - privSpec = new RSAPrivateCrtKeySpec(mod.getValue(), pubExp.getValue(), privExp.getValue(), p1.getValue(), p2.getValue(), exp1.getValue(), - exp2.getValue(), crtCoef.getValue()); - } - else if ( type.equals("DSA") ) { - ASN1Integer p = (ASN1Integer) seq.getObjectAt(1); - ASN1Integer q = (ASN1Integer) seq.getObjectAt(2); - ASN1Integer g = (ASN1Integer) seq.getObjectAt(3); - ASN1Integer y = (ASN1Integer) seq.getObjectAt(4); - ASN1Integer x = (ASN1Integer) seq.getObjectAt(5); - privSpec = new DSAPrivateKeySpec(x.getValue(), p.getValue(), q.getValue(), g.getValue()); - pubSpec = new DSAPublicKeySpec(y.getValue(), p.getValue(), q.getValue(), g.getValue()); - } - else if ( type.equals("ECDSA") ) { - return readECPrivateKey(input); - } - else { - throw new IllegalStateException("unsupported type: " + type); - } - KeyFactory fact = SecurityHelper.getKeyFactory(type); - return new KeyPair(fact.generatePublic(pubSpec), fact.generatePrivate(privSpec)); + return readPrivateKey(type, mockPrivateKeyInfo(type, input)); } - // d2i_PrivateKey_bio - public static KeyPair readPrivateKey(byte[] input) throws IOException, - NoSuchAlgorithmException, InvalidKeySpecException { - KeyPair key = null; - try { - key = readRSAPrivateKey(input); - } - catch (NoSuchAlgorithmException e) { throw e; /* should not happen */ } - catch (InvalidKeySpecException e) { - // ignore - } - if (key == null) { - try { - key = readDSAPrivateKey(input); - } - catch (NoSuchAlgorithmException e) { throw e; /* should not happen */ } - catch (InvalidKeySpecException e) { - // ignore - } + private static PrivateKeyInfo mockPrivateKeyInfo(final Type type, final byte[] input) throws IOException { + assert type != null; + return new PrivateKeyInfo(null, new ASN1InputStream(input).readObject()); + } + + public static KeyPair readPrivateKey(final Type type, final PrivateKeyInfo keyInfo) + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + KeySpec pubSpec; KeySpec privSpec; ASN1Sequence seq; + switch (type) { + case RSA: + seq = (ASN1Sequence) keyInfo.parsePrivateKey(); + ASN1Integer mod = (ASN1Integer) seq.getObjectAt(1); + ASN1Integer pubExp = (ASN1Integer) seq.getObjectAt(2); + ASN1Integer privExp = (ASN1Integer) seq.getObjectAt(3); + ASN1Integer p1 = (ASN1Integer) seq.getObjectAt(4); + ASN1Integer p2 = (ASN1Integer) seq.getObjectAt(5); + ASN1Integer exp1 = (ASN1Integer) seq.getObjectAt(6); + ASN1Integer exp2 = (ASN1Integer) seq.getObjectAt(7); + ASN1Integer crtCoef = (ASN1Integer) seq.getObjectAt(8); + pubSpec = new RSAPublicKeySpec(mod.getValue(), pubExp.getValue()); + privSpec = new RSAPrivateCrtKeySpec( + mod.getValue(), pubExp.getValue(), privExp.getValue(), + p1.getValue(), p2.getValue(), + exp1.getValue(), exp2.getValue(), crtCoef.getValue()); + break; + case DSA: + seq = (ASN1Sequence) keyInfo.parsePrivateKey(); + ASN1Integer p = (ASN1Integer) seq.getObjectAt(1); + ASN1Integer q = (ASN1Integer) seq.getObjectAt(2); + ASN1Integer g = (ASN1Integer) seq.getObjectAt(3); + ASN1Integer y = (ASN1Integer) seq.getObjectAt(4); + ASN1Integer x = (ASN1Integer) seq.getObjectAt(5); + privSpec = new DSAPrivateKeySpec(x.getValue(), p.getValue(), q.getValue(), g.getValue()); + pubSpec = new DSAPublicKeySpec(y.getValue(), p.getValue(), q.getValue(), g.getValue()); + break; + case EC: + return readECPrivateKey(SecurityHelper.getKeyFactory("EC"), keyInfo); + default: + throw new AssertionError("unexpected key type: " + type); } - return key; + final KeyFactory keyFactory = SecurityHelper.getKeyFactory(type.name()); + return new KeyPair(keyFactory.generatePublic(pubSpec), keyFactory.generatePrivate(privSpec)); } // d2i_PUBKEY_bio - public static PublicKey readPublicKey(byte[] input) throws IOException, - NoSuchAlgorithmException, InvalidKeySpecException { - PublicKey key = null; - try { - key = readRSAPublicKey(input); + public static PublicKey readPublicKey(final byte[] input) throws IOException { + try (Reader in = new InputStreamReader(new ByteArrayInputStream(input))) { + Object pemObject = new PEMParser(in).readObject(); + SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemObject); + return new JcaPEMKeyConverter().getPublicKey(publicKeyInfo); } - catch (NoSuchAlgorithmException e) { throw e; /* should not happen */ } - catch (InvalidKeySpecException e) { - // ignore - } - if (key == null) { - try { - key = readDSAPublicKey(input); - } - catch (NoSuchAlgorithmException e) { throw e; /* should not happen */ } - catch (InvalidKeySpecException e) { - // ignore - } - } - return key; } // d2i_RSAPrivateKey_bio @@ -170,8 +154,9 @@ public static KeyPair readRSAPrivateKey(final byte[] input) public static KeyPair readRSAPrivateKey(final KeyFactory rsaFactory, final byte[] input) throws IOException, InvalidKeySpecException { - ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject(); - if ( seq.size() == 9 ) { + ASN1Sequence seq; + ASN1Primitive obj = new ASN1InputStream(input).readObject(); + if (obj instanceof ASN1Sequence && (seq = (ASN1Sequence) obj).size() == 9) { BigInteger mod = ((ASN1Integer) seq.getObjectAt(1)).getValue(); BigInteger pubexp = ((ASN1Integer) seq.getObjectAt(2)).getValue(); BigInteger privexp = ((ASN1Integer) seq.getObjectAt(3)).getValue(); @@ -195,8 +180,9 @@ public static PublicKey readRSAPublicKey(final byte[] input) public static PublicKey readRSAPublicKey(final KeyFactory rsaFactory, final byte[] input) throws IOException, InvalidKeySpecException { - ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject(); - if ( seq.size() == 2 ) { + ASN1Sequence seq; + ASN1Primitive obj = new ASN1InputStream(input).readObject(); + if (obj instanceof ASN1Sequence && (seq = (ASN1Sequence) obj).size() == 2) { BigInteger mod = ((ASN1Integer) seq.getObjectAt(0)).getValue(); BigInteger pubexp = ((ASN1Integer) seq.getObjectAt(1)).getValue(); return rsaFactory.generatePublic(new RSAPublicKeySpec(mod, pubexp)); @@ -212,8 +198,9 @@ public static KeyPair readDSAPrivateKey(final byte[] input) public static KeyPair readDSAPrivateKey(final KeyFactory dsaFactory, final byte[] input) throws IOException, InvalidKeySpecException { - ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject(); - if ( seq.size() == 6 ) { + ASN1Sequence seq; + ASN1Primitive obj = new ASN1InputStream(input).readObject(); + if (obj instanceof ASN1Sequence && (seq = (ASN1Sequence) obj).size() == 6) { BigInteger p = ((ASN1Integer) seq.getObjectAt(1)).getValue(); BigInteger q = ((ASN1Integer) seq.getObjectAt(2)).getValue(); BigInteger g = ((ASN1Integer) seq.getObjectAt(3)).getValue(); @@ -234,13 +221,25 @@ public static PublicKey readDSAPublicKey(final byte[] input) public static PublicKey readDSAPublicKey(final KeyFactory dsaFactory, final byte[] input) throws IOException, InvalidKeySpecException { - ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(input).readObject(); - if ( seq.size() == 4 ) { - BigInteger y = ((ASN1Integer) seq.getObjectAt(0)).getValue(); - BigInteger p = ((ASN1Integer) seq.getObjectAt(1)).getValue(); - BigInteger q = ((ASN1Integer) seq.getObjectAt(2)).getValue(); - BigInteger g = ((ASN1Integer) seq.getObjectAt(3)).getValue(); - return dsaFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); + ASN1Sequence seq; + ASN1Primitive obj = new ASN1InputStream(input).readObject(); + if (obj instanceof ASN1Sequence) { + seq = (ASN1Sequence) obj; + if (seq.size() == 4) { + BigInteger y = ((ASN1Integer) seq.getObjectAt(0)).getValue(); + BigInteger p = ((ASN1Integer) seq.getObjectAt(1)).getValue(); + BigInteger q = ((ASN1Integer) seq.getObjectAt(2)).getValue(); + BigInteger g = ((ASN1Integer) seq.getObjectAt(3)).getValue(); + return dsaFactory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); + } else if (seq.size() == 2 && seq.getObjectAt(1) instanceof DERBitString) { + ASN1Integer y = (ASN1Integer) + new ASN1InputStream(((DERBitString) seq.getObjectAt(1)).getBytes()).readObject(); + seq = (ASN1Sequence) ((ASN1Sequence) seq.getObjectAt(0)).getObjectAt(1); + BigInteger p = ((ASN1Integer) seq.getObjectAt(0)).getValue(); + BigInteger q = ((ASN1Integer) seq.getObjectAt(1)).getValue(); + BigInteger g = ((ASN1Integer) seq.getObjectAt(2)).getValue(); + return dsaFactory.generatePublic(new DSAPublicKeySpec(y.getPositiveValue(), p, q, g)); + } } return null; } @@ -256,25 +255,32 @@ public static DHParameterSpec readDHParameter(final byte[] input) throws IOExcep public static KeyPair readECPrivateKey(final byte[] input) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { - return readECPrivateKey(SecurityHelper.getKeyFactory("ECDSA"), input); + return readECPrivateKey(SecurityHelper.getKeyFactory("EC"), input); } - public static KeyPair readECPrivateKey(final KeyFactory ecFactory, final byte[] input) + public static KeyPair readECPrivateKey(final KeyFactory keyFactory, final byte[] input) + throws IOException, InvalidKeySpecException { + return readECPrivateKey(keyFactory, mockPrivateKeyInfo(Type.EC, input)); + } + + public static KeyPair readECPrivateKey(final KeyFactory keyFactory, final PrivateKeyInfo keyInfo) throws IOException, InvalidKeySpecException { try { - ECPrivateKeyStructure pKey = new ECPrivateKeyStructure((ASN1Sequence) ASN1Primitive.fromByteArray(input)); - AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, pKey.getParameters()); - PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey.toASN1Primitive()); - SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, pKey.getPublicKey().getBytes()); - PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privInfo.getEncoded()); - X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo.getEncoded()); - //KeyFactory fact = KeyFactory.getInstance("ECDSA", provider); + ASN1Sequence seq = ASN1Sequence.getInstance(keyInfo.parsePrivateKey()); - ECPrivateKey privateKey = (ECPrivateKey) ecFactory.generatePrivate(privSpec); - if ( algId.getParameters() instanceof ASN1ObjectIdentifier ) { + org.bouncycastle.asn1.sec.ECPrivateKey key = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(seq); + AlgorithmIdentifier algId = keyInfo.getPrivateKeyAlgorithm(); + if (algId == null) { // mockPrivateKeyInfo + algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, key.getParameters()); + } + final SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(algId, key.getPublicKey().getBytes()); + final PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, key); + + ECPrivateKey privateKey = (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privInfo.getEncoded())); + if (algId.getParameters() instanceof ASN1ObjectIdentifier) { privateKey = ECPrivateKeyWithName.wrap(privateKey, (ASN1ObjectIdentifier) algId.getParameters()); } - return new KeyPair(ecFactory.generatePublic(pubSpec), privateKey); + return new KeyPair(keyFactory.generatePublic(new X509EncodedKeySpec(pubInfo.getEncoded())), privateKey); } catch (ClassCastException ex) { throw new IOException("wrong ASN.1 object found in stream", ex); @@ -285,28 +291,40 @@ public static KeyPair readECPrivateKey(final KeyFactory ecFactory, final byte[] } public static byte[] toDerRSAKey(RSAPublicKey pubKey, RSAPrivateCrtKey privKey) throws IOException { - ASN1EncodableVector vec = new ASN1EncodableVector(); if ( pubKey != null && privKey == null ) { - vec.add(new ASN1Integer(pubKey.getModulus())); - vec.add(new ASN1Integer(pubKey.getPublicExponent())); + return toDerRSAPublicKey(pubKey); } - else { - vec.add(new ASN1Integer(BigInteger.ZERO)); - vec.add(new ASN1Integer(privKey.getModulus())); - vec.add(new ASN1Integer(privKey.getPublicExponent())); - vec.add(new ASN1Integer(privKey.getPrivateExponent())); - vec.add(new ASN1Integer(privKey.getPrimeP())); - vec.add(new ASN1Integer(privKey.getPrimeQ())); - vec.add(new ASN1Integer(privKey.getPrimeExponentP())); - vec.add(new ASN1Integer(privKey.getPrimeExponentQ())); - vec.add(new ASN1Integer(privKey.getCrtCoefficient())); - } - return new DLSequence(vec).getEncoded(); + ASN1EncodableVector vec = new ASN1EncodableVector(); + vec.add(new ASN1Integer(BigInteger.ZERO)); + vec.add(new ASN1Integer(privKey.getModulus())); + vec.add(new ASN1Integer(privKey.getPublicExponent())); + vec.add(new ASN1Integer(privKey.getPrivateExponent())); + vec.add(new ASN1Integer(privKey.getPrimeP())); + vec.add(new ASN1Integer(privKey.getPrimeQ())); + vec.add(new ASN1Integer(privKey.getPrimeExponentP())); + vec.add(new ASN1Integer(privKey.getPrimeExponentQ())); + vec.add(new ASN1Integer(privKey.getCrtCoefficient())); + return new DERSequence(vec).toASN1Primitive().getEncoded(ASN1Encoding.DER); + } + + public static byte[] toDerRSAPublicKey(final RSAPublicKey pubKey) throws IOException { + // pubKey.getEncoded() : + return KeyUtil.getEncodedSubjectPublicKeyInfo( + new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), toASN1Primitive(pubKey) + ); + } + + public static ASN1Sequence toASN1Primitive(final RSAPublicKey publicKey) { + assert publicKey != null : "null public key"; + ASN1EncodableVector vec = new ASN1EncodableVector(); + vec.add(new ASN1Integer(publicKey.getModulus())); + vec.add(new ASN1Integer(publicKey.getPublicExponent())); + return new DERSequence(vec); } public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) throws IOException { if ( pubKey != null && privKey == null ) { - return pubKey.getEncoded(); + return toDerDSAPublicKey(pubKey); } if ( privKey != null && pubKey != null ) { ASN1EncodableVector vec = new ASN1EncodableVector(); @@ -317,12 +335,38 @@ public static byte[] toDerDSAKey(DSAPublicKey pubKey, DSAPrivateKey privKey) thr vec.add(new ASN1Integer(params.getG())); vec.add(new ASN1Integer(pubKey.getY())); vec.add(new ASN1Integer(privKey.getX())); - return new DLSequence(vec).getEncoded(); + return new DERSequence(vec).toASN1Primitive().getEncoded(ASN1Encoding.DER); } if ( privKey == null ) { throw new IllegalArgumentException("private key as well as public key are null"); } - return privKey.getEncoded(); + final DSAParams params = privKey.getParams(); + return new PrivateKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, + new DSAParameter(params.getP(), params.getQ(), params.getG())), + new ASN1Integer(privKey.getX()) + ).getEncoded(ASN1Encoding.DER); + } + + public static byte[] toDerDSAPublicKey(final DSAPublicKey pubKey) throws IOException { + // pubKey.getEncoded() : + final DSAParams params = pubKey.getParams(); + if (params == null) { + return new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa), + toASN1Primitive(pubKey) + ).getEncoded(ASN1Encoding.DER); + } + return new SubjectPublicKeyInfo( + new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, + new DSAParameter(params.getP(), params.getQ(), params.getG()) + ), + toASN1Primitive(pubKey) + ).getEncoded(ASN1Encoding.DER); + } + + public static ASN1Primitive toASN1Primitive(DSAPublicKey pubKey) { + return new ASN1Integer(pubKey.getY()); } public static byte[] toDerDHKey(BigInteger p, BigInteger g) throws IOException { @@ -332,5 +376,3 @@ public static byte[] toDerDHKey(BigInteger p, BigInteger g) throws IOException { return new DLSequence(vec).getEncoded(); } } - - diff --git a/src/main/java/org/jruby/ext/openssl/impl/Signed.java b/src/main/java/org/jruby/ext/openssl/impl/Signed.java index 3f7ee1d0..1dba04bf 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/Signed.java +++ b/src/main/java/org/jruby/ext/openssl/impl/Signed.java @@ -292,13 +292,13 @@ public static Signed fromASN1(ASN1Encodable content) throws PKCS7Exception{ int index = 3; ASN1Encodable tmp = sequence.getObjectAt(index); if((tmp instanceof ASN1TaggedObject) && ((ASN1TaggedObject)tmp).getTagNo() == 0) { - certificates = ((ASN1TaggedObject)tmp).getObject(); + certificates = ((ASN1TaggedObject)tmp).getBaseObject().toASN1Primitive(); index++; } tmp = sequence.getObjectAt(index); if((tmp instanceof ASN1TaggedObject) && ((ASN1TaggedObject)tmp).getTagNo() == 1) { - crls = ((ASN1TaggedObject)tmp).getObject(); + crls = ((ASN1TaggedObject)tmp).getBaseObject().toASN1Primitive(); index++; } @@ -354,19 +354,21 @@ private static X509AuxCertificate certificateFromASN1(ASN1Encodable current) thr } private static Set algorithmIdentifiersFromASN1Set(ASN1Encodable content) { - ASN1Set set = (ASN1Set)content; - Set result = new HashSet(); - for(Enumeration e = set.getObjects(); e.hasMoreElements();) { - result.add(AlgorithmIdentifier.getInstance(e.nextElement())); + ASN1Set set = (ASN1Set) content; + final int len = set.size(); + Set result = new HashSet<>(len); + for (int i = 0; i < len; i++) { + result.add(AlgorithmIdentifier.getInstance(set.getObjectAt(i))); } return result; } private static Collection signerInfosFromASN1Set(ASN1Encodable content) { - ASN1Set set = (ASN1Set)content; - Collection result = new ArrayList(); - for(Enumeration e = set.getObjects(); e.hasMoreElements();) { - result.add(SignerInfoWithPkey.getInstance(e.nextElement())); + ASN1Set set = (ASN1Set) content; + final int len = set.size(); + Collection result = new ArrayList<>(); + for (int i = 0; i < len; i++) { + result.add(SignerInfoWithPkey.getInstance(set.getObjectAt(i))); } return result; } diff --git a/src/main/java/org/jruby/ext/openssl/impl/SignerInfoWithPkey.java b/src/main/java/org/jruby/ext/openssl/impl/SignerInfoWithPkey.java index 3efd3422..8b8fd66f 100644 --- a/src/main/java/org/jruby/ext/openssl/impl/SignerInfoWithPkey.java +++ b/src/main/java/org/jruby/ext/openssl/impl/SignerInfoWithPkey.java @@ -72,11 +72,15 @@ public class SignerInfoWithPkey implements ASN1Encodable { private ASN1OctetString encryptedDigest; private ASN1Set unauthenticatedAttributes; - public static SignerInfoWithPkey getInstance(Object o) { - if(o instanceof SignerInfo) { - return (SignerInfoWithPkey)o; - } else if (o instanceof ASN1Sequence) { - return new SignerInfoWithPkey((ASN1Sequence)o); + public static SignerInfoWithPkey getInstance(ASN1Encodable o) { + if (o instanceof SignerInfo) { + final SignerInfo info = (SignerInfo) o; + return new SignerInfoWithPkey(info.getVersion(), info.getIssuerAndSerialNumber(), info.getDigestAlgorithm(), + info.getAuthenticatedAttributes(), info.getDigestEncryptionAlgorithm(), + info.getEncryptedDigest(), info.getUnauthenticatedAttributes()); + } + if (o instanceof ASN1Sequence) { + return new SignerInfoWithPkey((ASN1Sequence) o); } throw new IllegalArgumentException("unknown object in factory: " + o.getClass().getName()); @@ -97,13 +101,13 @@ public SignerInfoWithPkey dup() { SignerInfoWithPkey() { } - public SignerInfoWithPkey(ASN1Integer version, - IssuerAndSerialNumber issuerAndSerialNumber, - AlgorithmIdentifier digAlgorithm, - ASN1Set authenticatedAttributes, - AlgorithmIdentifier digEncryptionAlgorithm, - ASN1OctetString encryptedDigest, - ASN1Set unauthenticatedAttributes) { + public SignerInfoWithPkey(ASN1Integer version, + IssuerAndSerialNumber issuerAndSerialNumber, + AlgorithmIdentifier digAlgorithm, + ASN1Set authenticatedAttributes, + AlgorithmIdentifier digEncryptionAlgorithm, + ASN1OctetString encryptedDigest, + ASN1Set unauthenticatedAttributes) { this.version = version; this.issuerAndSerialNumber = issuerAndSerialNumber; this.digAlgorithm = digAlgorithm; @@ -113,8 +117,8 @@ public SignerInfoWithPkey(ASN1Integer version, this.unauthenticatedAttributes = unauthenticatedAttributes; } - public SignerInfoWithPkey(ASN1Sequence seq) { - Enumeration e = seq.getObjects(); + SignerInfoWithPkey(ASN1Sequence seq) { + Enumeration e = seq.getObjects(); version = (ASN1Integer)e.nextElement(); issuerAndSerialNumber = IssuerAndSerialNumber.getInstance(e.nextElement()); @@ -122,7 +126,7 @@ public SignerInfoWithPkey(ASN1Sequence seq) { Object obj = e.nextElement(); - if(obj instanceof ASN1TaggedObject) { + if (obj instanceof ASN1TaggedObject) { authenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)obj, false); digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement()); diff --git a/src/main/java/org/jruby/ext/openssl/util/CryptoSecurity.java b/src/main/java/org/jruby/ext/openssl/util/CryptoSecurity.java deleted file mode 100644 index e1764dc8..00000000 --- a/src/main/java/org/jruby/ext/openssl/util/CryptoSecurity.java +++ /dev/null @@ -1,151 +0,0 @@ -/***** BEGIN LICENSE BLOCK ***** - * Version: EPL 1.0/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Eclipse Public - * License Version 1.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.eclipse.org/legal/epl-v10.html - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * Copyright (C) 2017 Karol Bucek - * - * Alternatively, the contents of this file may be used under the terms of - * either of the GNU General Public License Version 2 or later (the "GPL"), - * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the EPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the EPL, the GPL or the LGPL. - ***** END LICENSE BLOCK *****/ -package org.jruby.ext.openssl.util; - -import org.jruby.ext.openssl.OpenSSL; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Security; - -/** - * JCE security helper for disabling (default) imposed cryptographic restrictions. - * - * Using this class might be in **contrast with the license agreement** that came with your JRE. - * - * It's preferable to install: - * - * "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" - * - * specific to your Java version! - * - * @see http://www.oracle.com/technetwork/java/javase/downloads/index.html - */ -public final class CryptoSecurity { - - private CryptoSecurity() { /* no instances */ } - - public static void disableJceRestrictions() { - unrestrictSecurity(); - setAllPermissionPolicy(); - } - - public static Boolean setAllPermissionPolicy() { - if ( ! OpenSSL.javaHotSpot() ) return false; - try { - final Class JceSecurity = Class.forName("javax.crypto.JceSecurity"); - - final Class CryptoPermissions = Class.forName("javax.crypto.CryptoPermissions"); - final Class CryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission"); - - Field defaultPolicy = JceSecurity.getDeclaredField("defaultPolicy"); - defaultPolicy.setAccessible(true); - - Field perms = CryptoPermissions.getDeclaredField("perms"); - perms.setAccessible(true); - - Field INSTANCE = CryptoAllPermission.getDeclaredField("INSTANCE"); - INSTANCE.setAccessible(true); - - synchronized (Security.class) { - final PermissionCollection defPolicy = (PermissionCollection) defaultPolicy.get(null); - final java.util.Map permsMap = (java.util.Map) perms.get(defPolicy); - if ( ! permsMap.isEmpty() ) { - permsMap.clear(); - defPolicy.add((Permission) INSTANCE.get(null)); - return true; - } - return false; - } - } - catch (ClassNotFoundException e) { - OpenSSL.debug("unable un-restrict jce security: ", e); - return null; - } - catch (Exception e) { - OpenSSL.debug("unable un-restrict jce security: "); - OpenSSL.debugStackTrace(e); - return null; - } - } - - public static Boolean unrestrictSecurity() { - if ( ! OpenSSL.javaHotSpot() ) return false; - if ( OpenSSL.javaVersion9(true) ) { - return unrestrictJceSecurity9(); - } - return unrestrictJceSecurity8(); - } - - static Boolean unrestrictJceSecurity9() { - try { - if (Security.getProperty("crypto.policy") == null) { - Security.setProperty("crypto.policy", "unlimited"); - return true; - } - return false; - } - catch (Exception e) { - OpenSSL.debug("unable un-restrict jce security: ", e); - return null; - } - } - - static Boolean unrestrictJceSecurity8() { - try { - final Class JceSecurity = Class.forName("javax.crypto.JceSecurity"); - - Field isRestricted = JceSecurity.getDeclaredField("isRestricted"); - - if (Modifier.isFinal(isRestricted.getModifiers())) { - Field modifiers = Field.class.getDeclaredField("modifiers"); - modifiers.setAccessible(true); - modifiers.setInt(isRestricted, isRestricted.getModifiers() & ~Modifier.FINAL); - } - - isRestricted.setAccessible(true); - if (isRestricted.getBoolean(null) == true) { - isRestricted.setBoolean(null, false); // isRestricted = false; - return true; - } - return false; - } - catch (ClassNotFoundException e) { - OpenSSL.debug("unable un-restrict jce security: ", e); - return null; - } - catch (Exception e) { - OpenSSL.debug("unable un-restrict jce security: "); - OpenSSL.debugStackTrace(e); - return null; - } - } - -} diff --git a/src/main/java/org/jruby/ext/openssl/x509store/CRL.java b/src/main/java/org/jruby/ext/openssl/x509store/CRL.java index 02ca49cb..7effd1b8 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/CRL.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/CRL.java @@ -36,12 +36,9 @@ */ public class CRL extends X509Object { - public /* final */ java.security.cert.CRL crl; + public final java.security.cert.CRL crl; - @Deprecated // not-used - public CRL() { /* */ } - - public CRL(X509CRL crl) { + public CRL(java.security.cert.CRL crl) { this.crl = crl; } diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Certificate.java b/src/main/java/org/jruby/ext/openssl/x509store/Certificate.java index 2bd58df5..c06ac1f6 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Certificate.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Certificate.java @@ -34,10 +34,10 @@ */ public class Certificate extends X509Object { - public final X509AuxCertificate x509; + public final X509AuxCertificate cert; public Certificate(final X509AuxCertificate cert) { - this.x509 = cert; + this.cert = cert; } @Override @@ -47,14 +47,16 @@ public int type() { @Override public boolean isName(final Name name) { - return name.equalToCertificateSubject(x509); + return name.equalToCertificateSubject(cert); } @Override public boolean matches(final X509Object other) { if (other instanceof Certificate) { final Certificate that = (Certificate) other; - return X509AuxCertificate.equalSubjects(this.x509, that.x509); + if (X509AuxCertificate.equalSubjects(this.cert, that.cert)) { + return this.cert.hashCode() == that.cert.hashCode(); + }; } return false; } @@ -63,7 +65,7 @@ public boolean matches(final X509Object other) { public int compareTo(final X509Object other) { int cmp = super.compareTo(other); if (cmp != 0) return cmp; - return x509.equals( ( (Certificate) other ).x509 ) ? 0 : -1; + return cert.equals( ((Certificate) other).cert ) ? 0 : -1; } }// X509_OBJECT_CERT diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Function1.java b/src/main/java/org/jruby/ext/openssl/x509store/Function1.java index 2d1d4f63..9785ca77 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Function1.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Function1.java @@ -33,11 +33,5 @@ * @author Ola Bini */ interface Function1 { - static class Empty implements Function1 { - public int call(Object arg0) { - return -1; - } - } - public static final Function1.Empty EMPTY = new Empty(); int call(T arg0) throws Exception; }// Function1 diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Function2.java b/src/main/java/org/jruby/ext/openssl/x509store/Function2.java index 8949a901..95d3f1c3 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Function2.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Function2.java @@ -33,11 +33,5 @@ * @author Ola Bini */ interface Function2 { - static class Empty implements Function2 { - public int call(Object arg0, Object arg1) { - return -1; - } - } - public static final Function2.Empty EMPTY = new Empty(); int call(T arg0, U arg1) throws Exception; }// Function2 diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Function3.java b/src/main/java/org/jruby/ext/openssl/x509store/Function3.java index e7a51454..cdffc5d2 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Function3.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Function3.java @@ -33,11 +33,5 @@ * @author Ola Bini */ interface Function3 { - static class Empty implements Function3 { - public int call(Object arg0,Object arg1,Object arg2) { - return -1; - } - } - public static final Function3.Empty EMPTY = new Empty(); int call(T arg0, U arg1, V arg2) throws Exception; }// Function3 diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Function4.java b/src/main/java/org/jruby/ext/openssl/x509store/Function4.java index 0726f297..8b213190 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Function4.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Function4.java @@ -33,11 +33,5 @@ * @author Ola Bini */ interface Function4 { - static class Empty implements Function4 { - public int call(Object arg0,Object arg1,Object arg2,Object arg3) { - return -1; - } - } - public static final Function4.Empty EMPTY = new Empty(); int call(T arg0, U arg1, V arg2, X arg3) throws Exception; }// Function4 diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Function5.java b/src/main/java/org/jruby/ext/openssl/x509store/Function5.java index 152e2fb7..c7fa7c6e 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Function5.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Function5.java @@ -33,11 +33,5 @@ * @author Ola Bini */ interface Function5 { - static class Empty implements Function5 { - public int call(Object arg0,Object arg1,Object arg2,Object arg3,Object arg4) { - return -1; - } - } - public static final Function5.Empty EMPTY = new Empty(); int call(T arg0, U arg1, V arg2, X arg3, Y arg4) throws Exception; }// Function5 diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Lookup.java b/src/main/java/org/jruby/ext/openssl/x509store/Lookup.java index efe09cb1..545a2347 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Lookup.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Lookup.java @@ -81,7 +81,7 @@ public Lookup(Ruby runtime, LookupMethod method) { this.runtime = runtime; final LookupMethod.NewItemFunction newItem = method.newItem; - if ( newItem != null && newItem != Function1.EMPTY ) { + if ( newItem != null ) { final int result; try { result = newItem.call(this); @@ -128,7 +128,7 @@ public static LookupMethod fileLookup() { public int control(final int cmd, final String argc, final long argl, final String[] ret) throws Exception { if ( method == null ) return -1; - if ( method.control != null && method.control != Function5.EMPTY ) { + if ( method.control != null ) { return method.control.call(this, Integer.valueOf(cmd), argc, Long.valueOf(argl), ret); } return 1; @@ -336,7 +336,7 @@ public int loadDefaultJavaCACertsFile(String certsFile) throws IOException, Gene try { // hardcode the keystore type, as we expect cacerts to be a java keystore // especially needed since Java 9 (getDefaultType on 11/13 is "pkcs12") - KeyStore keystore = SecurityHelper.getKeyStore("jks"); + KeyStore keystore = SecurityHelper.getKeyStore("JKS"); // null password - as the cacerts file isn't password protected keystore.load(fin, null); PKIXParameters params = new PKIXParameters(keystore); @@ -364,7 +364,7 @@ private String envEntry(final String key) { * c: X509_LOOKUP_free */ public void free() throws Exception { - if ( method != null && method.free != null && method.free != Function1.EMPTY ) { + if ( method != null && method.free != null ) { method.free.call(this); } } @@ -374,7 +374,7 @@ public void free() throws Exception { */ public int init() throws Exception { if ( method == null ) return 0; - if ( method.init != null && method.init != Function1.EMPTY ) { + if ( method.init != null ) { return method.init.call(this); } return 1; @@ -384,7 +384,7 @@ public int init() throws Exception { * c: X509_LOOKUP_by_subject */ public int bySubject(final int type, final Name name, final X509Object[] ret) throws Exception { - if ( method == null || method.getBySubject == null || method.getBySubject == Function4.EMPTY ) { + if ( method == null || method.getBySubject == null ) { return X509_LU_FAIL; } if ( skip ) return 0; @@ -395,7 +395,7 @@ public int bySubject(final int type, final Name name, final X509Object[] ret) th * c: X509_LOOKUP_by_issuer_serial */ public int byIssuerSerialNumber(final int type, final Name name, final BigInteger serial, final X509Object[] ret) throws Exception { - if ( method == null || method.getByIssuerSerialNumber == null || method.getByIssuerSerialNumber == Function5.EMPTY ) { + if ( method == null || method.getByIssuerSerialNumber == null ) { return X509_LU_FAIL; } return method.getByIssuerSerialNumber.call(this, Integer.valueOf(type), name, serial, ret); @@ -405,7 +405,7 @@ public int byIssuerSerialNumber(final int type, final Name name, final BigIntege * c: X509_LOOKUP_by_fingerprint */ public int byFingerprint(final int type, final String bytes, final X509Object[] ret) throws Exception { - if ( method == null || method.getByFingerprint == null || method.getByFingerprint == Function4.EMPTY ) { + if ( method == null || method.getByFingerprint == null ) { return X509_LU_FAIL; } return method.getByFingerprint.call(this, Integer.valueOf(type), bytes, ret); @@ -415,7 +415,7 @@ public int byFingerprint(final int type, final String bytes, final X509Object[] * c: X509_LOOKUP_by_alias */ public int byAlias(final int type, final String alias, final X509Object[] ret) throws Exception { - if ( method == null || method.getByAlias == null || method.getByAlias == Function4.EMPTY ) { + if ( method == null || method.getByAlias == null ) { return X509_LU_FAIL; } return method.getByAlias.call(this, Integer.valueOf(type), alias, ret); @@ -427,7 +427,7 @@ public int byAlias(final int type, final String alias, final X509Object[] ret) t public int shutdown() throws Exception { if ( method == null ) return 0; - if ( method.shutdown != null && method.shutdown != Function1.EMPTY ) { + if ( method.shutdown != null ) { return method.shutdown.call(this); } return 1; @@ -470,7 +470,7 @@ public int call(final Lookup ctx, final Integer cmd, final String argp, final Nu file = ctx.envEntry(X509_CERT_FILE_EVP); // ENV['SSL_CERT_FILE'] } catch (RuntimeException e) { - OpenSSL.debug(ctx.runtime, "failed to read env " + X509_CERT_FILE_EVP, e); + OpenSSL.debugStackTrace(ctx.runtime, "failed to read env " + X509_CERT_FILE_EVP, e); } if (file == null) { file = X509_CERT_FILE.replace('/', File.separatorChar); diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Name.java b/src/main/java/org/jruby/ext/openssl/x509store/Name.java index 9cb4894a..121642b3 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Name.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Name.java @@ -35,6 +35,7 @@ import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1Encoding; +import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.jce.provider.X509CertificateObject; @@ -58,12 +59,12 @@ public Name(final X500Name name) { this.name = name; } - public static int hash(final X500Name name) throws IOException { + public static long hashOld(final X500Name name) throws IOException { try { final byte[] bytes = name.getEncoded(); MessageDigest md5 = SecurityHelper.getMessageDigest("MD5"); final byte[] digest = md5.digest(bytes); - int result = 0; + long result = 0; result |= digest[3] & 0xff; result <<= 8; result |= digest[2] & 0xff; result <<= 8; result |= digest[1] & 0xff; result <<= 8; @@ -75,9 +76,43 @@ public static int hash(final X500Name name) throws IOException { } } - private transient int hash = 0; + public static long hash(final X500Name canonicalName) throws IOException { + try { + final byte[] bytes = canonicalName.getEncoded(); + MessageDigest sha = SecurityHelper.getMessageDigest("SHA1"); + int n = getLeadingTLLength(bytes); + sha.update(bytes, n, bytes.length - n); //canonical form does not include leading SEQUENCE Tag-Length + final byte[] digest = sha.digest(); + long result = 0; + result |= digest[3] & 0xff; result <<= 8; + result |= digest[2] & 0xff; result <<= 8; + result |= digest[1] & 0xff; result <<= 8; + result |= digest[0] & 0xff; + return result & 0xffffffff; + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static int getLeadingTLLength(byte[] bytes) throws IOException { + if (bytes.length <= 1) { + return bytes.length; //should not happen tough + } + byte length = bytes[1]; + + if ((length & 0x80) == 0x80) { + // long form: Two to 127 octets. Bit 8 of first octet has value "1" and + // bits 7-1 give the number of additional length octets. + int size = length & 0x7f; + return 1 + 1 + size; + } + return 2; //short form: 1 byte tag, 1 byte length + } + + private transient long hash = 0; - public final int hash() { + public final long hash() { try { return hash == 0 ? hash = hash(name) : hash; } @@ -93,7 +128,7 @@ public final int hash() { * c: X509_NAME_hash */ @Override - public int hashCode() { return hash(); } + public int hashCode() { return (int)hash(); } @Override public boolean equals(final Object that) { diff --git a/src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java b/src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java index 2b689282..b515a9d8 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/PEMInputOutput.java @@ -56,6 +56,7 @@ import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPrivateCrtKey; +import java.security.spec.DSAPublicKeySpec; import java.security.spec.ECParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; @@ -64,6 +65,7 @@ import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; +import java.util.Enumeration; import java.util.List; import java.util.StringTokenizer; @@ -79,12 +81,10 @@ import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; -import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1TaggedObject; import org.bouncycastle.asn1.pkcs.PKCS12PBEParams; import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; import org.bouncycastle.asn1.ASN1Encodable; -import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1OutputStream; @@ -127,6 +127,7 @@ import org.jruby.ext.openssl.Cipher.Algorithm; import org.jruby.ext.openssl.impl.ASN1Registry; import org.jruby.ext.openssl.impl.CipherSpec; +import org.jruby.ext.openssl.impl.PKey.Type; import org.jruby.ext.openssl.impl.PKCS10Request; import org.jruby.ext.openssl.SecurityHelper; import org.jruby.ext.openssl.util.ByteArrayOutputStream; @@ -338,7 +339,7 @@ else if ( line.indexOf(BEG_STRING_DSA) != -1 ) { } else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) { try { - return readKeyPair(reader, passwd, "ECDSA", BEF_E + PEM_STRING_ECPRIVATEKEY); + return readKeyPair(reader, passwd, "EC", BEF_E + PEM_STRING_ECPRIVATEKEY); } catch (Exception e) { throw mapReadException("problem creating DSA private key: ", e); @@ -347,9 +348,9 @@ else if ( line.indexOf(BEG_STRING_ECPRIVATEKEY) != -1) { else if ( line.indexOf(BEG_STRING_PKCS8INF) != -1) { try { byte[] bytes = readBase64Bytes(reader, BEF_E + PEM_STRING_PKCS8INF); - PrivateKeyInfo info = PrivateKeyInfo.getInstance(bytes); - String type = getPrivateKeyTypeFromObjectId(info.getPrivateKeyAlgorithm().getAlgorithm()); - return org.jruby.ext.openssl.impl.PKey.readPrivateKey(((ASN1Object) info.parsePrivateKey()).getEncoded(ASN1Encoding.DER), type); + final PrivateKeyInfo keyInfo = PrivateKeyInfo.getInstance(bytes); + final Type type = getPrivateKeyType(keyInfo.getPrivateKeyAlgorithm()); + return org.jruby.ext.openssl.impl.PKey.readPrivateKey(type, keyInfo); } catch (Exception e) { throw mapReadException("problem creating private key: ", e); @@ -466,9 +467,17 @@ public static DSAPublicKey readDSAPubKey(Reader in) throws IOException { final String BEG_STRING_DSA_PUBLIC = BEF_G + PEM_STRING_DSA_PUBLIC; final BufferedReader reader = makeBuffered(in); String line; while ( ( line = reader.readLine() ) != null ) { - if ( line.indexOf(BEG_STRING_DSA_PUBLIC) != -1 ) { + if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) { + try { + return readDSAPublicKey(reader, BEF_E + PEM_STRING_PUBLIC); + } + catch (Exception e) { + throw mapReadException("problem creating DSA public key: ", e); + } + } + else if ( line.indexOf(BEG_STRING_DSA_PUBLIC) != -1 ) { try { - return (DSAPublicKey) readPublicKey(reader, "DSA", BEF_E + PEM_STRING_DSA_PUBLIC); + return readDSAPublicKey(reader, BEF_E + PEM_STRING_DSA_PUBLIC); } catch (Exception e) { throw mapReadException("problem creating DSA public key: ", e); @@ -558,7 +567,7 @@ public static RSAPublicKey readRSAPublicKey(Reader in, char[] f) throw mapReadException("problem creating RSA public key: ", e); } } - else if ( line.indexOf(BEF_G+PEM_STRING_RSA_PUBLIC) != -1 ) { + else if ( line.indexOf(BEF_G + PEM_STRING_RSA_PUBLIC) != -1 ) { try { return (RSAPublicKey) readPublicKey(reader, "RSA", BEF_E + PEM_STRING_RSA_PUBLIC); } @@ -595,7 +604,7 @@ public static ECPublicKey readECPubKey(Reader in) throws IOException { while ( ( line = reader.readLine() ) != null ) { if ( line.indexOf(BEG_STRING_EC_PUBLIC) != -1 ) { try { - return (ECPublicKey) readPublicKey(reader, "ECDSA", BEF_E + "EC PUBLIC KEY"); + return (ECPublicKey) readPublicKey(reader, "EC", BEF_E + "EC PUBLIC KEY"); } catch (Exception e) { throw mapReadException("problem creating ECDSA public key: ", e); @@ -611,7 +620,7 @@ public static ECPublicKey readECPublicKey(final Reader in, final char[] passwd) while ( ( line = reader.readLine() ) != null ) { if ( line.indexOf(BEG_STRING_PUBLIC) != -1 ) { try { - return (ECPublicKey) readPublicKey(reader, "ECDSA", BEF_E + PEM_STRING_PUBLIC); + return (ECPublicKey) readPublicKey(reader, "EC", BEF_E + PEM_STRING_PUBLIC); } catch (Exception e) { throw mapReadException("problem creating ECDSA public key: ", e); @@ -628,7 +637,7 @@ public static KeyPair readECPrivateKey(final Reader in, final char[] passwd) while ( ( line = reader.readLine() ) != null ) { if ( line.indexOf(BEG_STRING_EC) != -1 ) { try { - return readKeyPair(reader, passwd, "ECDSA", BEF_E + "EC PRIVATE KEY"); + return readKeyPair(reader, passwd, "EC", BEF_E + "EC PRIVATE KEY"); } catch (Exception e) { throw mapReadException("problem creating ECDSA private key: ", e); @@ -994,7 +1003,7 @@ public static void writeDSAPrivateKey(Writer _out, DSAPrivateKey obj, CipherSpec BufferedWriter out = makeBuffered(_out); PrivateKeyInfo info = PrivateKeyInfo.getInstance(new ASN1InputStream(getEncoded(obj)).readObject()); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ASN1OutputStream aOut = new ASN1OutputStream(bOut); + ASN1OutputStream aOut = ASN1OutputStream.create(bOut); DSAParameter p = DSAParameter.getInstance(info.getPrivateKeyAlgorithm().getParameters()); ASN1EncodableVector v = new ASN1EncodableVector(); @@ -1146,7 +1155,7 @@ public static void writeDHParameters(Writer _out, DHParameterSpec params) throws } ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - ASN1OutputStream aOut = new ASN1OutputStream(bOut); + ASN1OutputStream aOut = ASN1OutputStream.create(bOut); aOut.writeObject(new DLSequence(v)); @@ -1161,6 +1170,8 @@ public static void writeDHParameters(Writer _out, DHParameterSpec params) throws private static String getPrivateKeyTypeFromObjectId(ASN1ObjectIdentifier oid) { if ( ASN1Registry.oid2nid(oid) == ASN1Registry.NID_rsaEncryption ) { return "RSA"; + } else if ( ASN1Registry.oid2nid(oid) == ASN1Registry.NID_X9_62_id_ecPublicKey ) { + return "EC"; } else { return "DSA"; } @@ -1170,9 +1181,7 @@ private static RSAPublicKey readRSAPublicKey(BufferedReader in, String endMarker Object asnObject = new ASN1InputStream(readBase64Bytes(in, endMarker)).readObject(); ASN1Sequence sequence = (ASN1Sequence) asnObject; org.bouncycastle.asn1.pkcs.RSAPublicKey rsaPubStructure = org.bouncycastle.asn1.pkcs.RSAPublicKey.getInstance(sequence); - RSAPublicKeySpec keySpec = new RSAPublicKeySpec( - rsaPubStructure.getModulus(), - rsaPubStructure.getPublicExponent()); + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaPubStructure.getModulus(), rsaPubStructure.getPublicExponent()); try { return (RSAPublicKey) SecurityHelper.getKeyFactory("RSA").generatePublic(keySpec); @@ -1182,6 +1191,26 @@ private static RSAPublicKey readRSAPublicKey(BufferedReader in, String endMarker return null; } + private static DSAPublicKey readDSAPublicKey(BufferedReader in, String endMarker) throws IOException { + Object asnObject = new ASN1InputStream(readBase64Bytes(in, endMarker)).readObject(); + Enumeration seq = ((ASN1Sequence) asnObject).getObjects(); + ASN1Integer y = ASN1Integer.getInstance(seq.nextElement()); + ASN1Integer p = ASN1Integer.getInstance(seq.nextElement()); + ASN1Integer q = ASN1Integer.getInstance(seq.nextElement()); + ASN1Integer g = ASN1Integer.getInstance(seq.nextElement()); + + DSAPublicKeySpec keySpec = new DSAPublicKeySpec( + y.getPositiveValue(), p.getPositiveValue(), q.getPositiveValue(), g.getPositiveValue() + ); + + try { + return (DSAPublicKey) SecurityHelper.getKeyFactory("DSA").generatePublic(keySpec); + } + catch (NoSuchAlgorithmException e) { /* ignore */ } + catch (InvalidKeySpecException e) { /* ignore */ } + return null; + } + private static PublicKey readPublicKey(byte[] input, String alg, String endMarker) throws IOException { KeySpec keySpec = new X509EncodedKeySpec(input); try { @@ -1198,7 +1227,7 @@ private static PublicKey readPublicKey(BufferedReader in, String alg, String end private static PublicKey readPublicKey(BufferedReader in, String endMarker) throws IOException { byte[] input = readBase64Bytes(in, endMarker); - String[] algs = { "RSA", "DSA", "ECDSA" }; + String[] algs = { "RSA", "DSA", "EC" }; for (int i = 0; i < algs.length; i++) { PublicKey key = readPublicKey(input, algs[i], endMarker); if (key != null) { @@ -1239,7 +1268,7 @@ else if ( line.contains(endMarker) ) { } else { keyBytes = decoded; } - return org.jruby.ext.openssl.impl.PKey.readPrivateKey(keyBytes, type); + return org.jruby.ext.openssl.impl.PKey.readPrivateKey(Type.valueOf(type), keyBytes); } private static byte[] decrypt(byte[] decoded, String dekInfo, char[] passwd) @@ -1334,8 +1363,8 @@ private static X509AuxCertificate readAuxCertificate(final BufferedReader in, fi else trust = Collections.emptyList(); if ( obj instanceof ASN1TaggedObject && ((ASN1TaggedObject) obj).getTagNo() == 0 ) { - reject = new ArrayList(); - final ASN1Sequence rejectSeq = (ASN1Sequence) ((ASN1TaggedObject) obj).getObject(); + final ASN1Sequence rejectSeq = (ASN1Sequence) ((ASN1TaggedObject) obj).getBaseObject().toASN1Primitive(); + reject = new ArrayList<>(rejectSeq.size()); for( int i = 0; i < rejectSeq.size(); i++ ) { reject.add( ((ASN1ObjectIdentifier) rejectSeq.getObjectAt(i)).getId() ); } @@ -1359,8 +1388,8 @@ private static X509AuxCertificate readAuxCertificate(final BufferedReader in, fi else keyid = null; if ( obj instanceof ASN1TaggedObject && ((ASN1TaggedObject) obj).getTagNo() == 1 ) { - other = new ArrayList(); - final ASN1Sequence otherSeq = (ASN1Sequence) ((ASN1TaggedObject) obj).getObject(); + final ASN1Sequence otherSeq = (ASN1Sequence) ((ASN1TaggedObject) obj).getBaseObject().toASN1Primitive(); + other = new ArrayList<>(otherSeq.size()); for( int i = 0; i < otherSeq.size(); i++ ) { other.add( (ASN1Primitive) otherSeq.getObjectAt(i) ); } @@ -1455,23 +1484,23 @@ private static CMSSignedData readPKCS7(BufferedReader in, char[] p, String endMa public static KeyFactory getKeyFactory(final AlgorithmIdentifier algId) throws NoSuchAlgorithmException { + return SecurityHelper.getKeyFactory(getPrivateKeyType(algId).name()); + } + private static Type getPrivateKeyType(final AlgorithmIdentifier algId) { final ASN1ObjectIdentifier algIdentifier = algId.getAlgorithm(); - String algorithm = null; - if ( X9ObjectIdentifiers.id_ecPublicKey.equals(algIdentifier) ) { - algorithm = "ECDSA"; + if (X9ObjectIdentifiers.id_ecPublicKey.equals(algIdentifier)) { + return Type.EC; } - else if ( PKCSObjectIdentifiers.rsaEncryption.equals(algIdentifier) ) { - algorithm = "RSA"; + if (PKCSObjectIdentifiers.rsaEncryption.equals(algIdentifier)) { + return Type.RSA; } - else if ( X9ObjectIdentifiers.id_dsa.equals(algIdentifier) ) { - algorithm = "DSA"; + if (X9ObjectIdentifiers.id_dsa.equals(algIdentifier)) { + return Type.DSA; } - if ( algorithm == null ) algorithm = algIdentifier.getId(); - - return SecurityHelper.getKeyFactory(algorithm); + return Type.valueOf(algIdentifier.getId()); } private static CertificateFactory getX509CertificateFactory() { diff --git a/src/main/java/org/jruby/ext/openssl/x509store/PKey.java b/src/main/java/org/jruby/ext/openssl/x509store/PKey.java index 69384619..80c1e017 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/PKey.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/PKey.java @@ -36,10 +36,7 @@ */ public class PKey extends X509Object { - public /* final */ java.security.PrivateKey pkey; - - @Deprecated // not-used - public PKey() { /* no-op */ } + public final java.security.PrivateKey pkey; public PKey(PrivateKey pkey) { this.pkey = pkey; @@ -48,4 +45,19 @@ public PKey(PrivateKey pkey) { public int type() { return X509Utils.X509_LU_PKEY; } + + @Override + public boolean isName(final Name nm) { + return false; + } + + @Override + public boolean matches(final X509Object other) { + if (other instanceof PKey) { + final PKey that = (PKey) other; + return this.pkey.equals( that.pkey ); + } + return false; + } + }// X509_OBJECT_PKEY diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Store.java b/src/main/java/org/jruby/ext/openssl/x509store/Store.java index 5d7456ca..57a04870 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Store.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Store.java @@ -39,8 +39,10 @@ import java.util.List; import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; import org.jruby.Ruby; +import org.jruby.ext.openssl.OpenSSL; /** * c: X509_STORE @@ -49,89 +51,52 @@ */ public class Store implements X509TrustManager { - public static interface VerifyFunction extends Function1 { - public static final VerifyFunction EMPTY = new VerifyFunction(){ - public int call(StoreContext context) { - return -1; - } - }; - } - public static interface VerifyCallbackFunction extends Function2 { - public static final VerifyCallbackFunction EMPTY = new VerifyCallbackFunction(){ - public int call(StoreContext context, Integer outcome) { - return -1; - } - }; - } - static interface GetIssuerFunction extends Function3 { - public static final GetIssuerFunction EMPTY = new GetIssuerFunction(){ - public int call(StoreContext context, X509AuxCertificate[] issuer, X509AuxCertificate cert) { - return -1; - } - }; - } - static interface CheckIssuedFunction extends Function3 { - public static final CheckIssuedFunction EMPTY = new CheckIssuedFunction(){ - public int call(StoreContext context, X509AuxCertificate cert, X509AuxCertificate issuer) throws Exception { - return -1; - } - }; - } - static interface CheckRevocationFunction extends Function1 { - public static final CheckRevocationFunction EMPTY = new CheckRevocationFunction(){ - public int call(StoreContext context) { - return -1; - } - }; - } - static interface GetCRLFunction extends Function3 { - public static final GetCRLFunction EMPTY = new GetCRLFunction(){ - public int call(StoreContext context, java.security.cert.X509CRL[] crls, X509AuxCertificate cert) { - return -1; - } - }; - } - static interface CheckCRLFunction extends Function2 { - public static final CheckCRLFunction EMPTY = new CheckCRLFunction(){ - public int call(StoreContext context, java.security.cert.X509CRL crl) { - return -1; - } - }; - } - static interface CertificateCRLFunction extends Function3 { - public static final CertificateCRLFunction EMPTY = new CertificateCRLFunction(){ - public int call(StoreContext context, java.security.cert.X509CRL crl, X509AuxCertificate cert) { - return -1; - } - }; - } - static interface CleanupFunction extends Function1 { - public static final CleanupFunction EMPTY = new CleanupFunction(){ - public int call(StoreContext context) { - return -1; - } - }; + public interface VerifyFunction extends Function1 { } + + public interface VerifyCallbackFunction extends Function2 { } + + interface GetIssuerFunction extends Function3 { } + + interface CheckIssuedFunction extends Function3 { } + + interface CheckRevocationFunction extends Function1 { } + + interface GetCRLFunction extends Function3 { } + + interface CheckCRLFunction extends Function2 { } + + interface CertificateCRLFunction extends Function3 { } + + interface CleanupFunction extends Function1 { } + + interface LookupCerts { + List call(StoreContext ctx, Name name) throws Exception; } // @Deprecated int cache = 1; // not-used - private volatile X509Object[] objects = new X509Object[0]; - private volatile Lookup[] certLookups = new Lookup[0]; + private static final X509Object[] NULL_OBJECTS = new X509Object[0]; + private static final Lookup[] NULL_LOOKUP = new Lookup[0]; - public final VerifyParameter verifyParameter; + private volatile X509Object[] objects = NULL_OBJECTS; + private volatile Lookup[] certLookups = NULL_LOOKUP; - VerifyFunction verify = VerifyFunction.EMPTY; - VerifyCallbackFunction verifyCallback = VerifyCallbackFunction.EMPTY; + final VerifyParameter verifyParameter; - GetIssuerFunction getIssuer = GetIssuerFunction.EMPTY; - CheckIssuedFunction checkIssued = CheckIssuedFunction.EMPTY; - CheckRevocationFunction checkRevocation = CheckRevocationFunction.EMPTY; - GetCRLFunction getCRL = GetCRLFunction.EMPTY; - CheckCRLFunction checkCRL = CheckCRLFunction.EMPTY; - CertificateCRLFunction certificateCRL = CertificateCRLFunction.EMPTY; - CleanupFunction cleanup = CleanupFunction.EMPTY; + VerifyFunction verify; + VerifyCallbackFunction verifyCallback; - private final List extraData; + GetIssuerFunction getIssuer; + CheckIssuedFunction checkIssued; + CheckRevocationFunction checkRevocation; + GetCRLFunction getCRL; + CheckCRLFunction checkCRL; + CertificateCRLFunction certificateCRL; + CleanupFunction cleanup; + + LookupCerts lookup_certs; // NOTE: for now always null here + + private final ArrayList extraData; /** * c: X509_STORE_new @@ -139,10 +104,7 @@ public int call(StoreContext context) { public Store() { verifyParameter = new VerifyParameter(); - extraData = new ArrayList(10); - this.extraData.add(null); this.extraData.add(null); this.extraData.add(null); - this.extraData.add(null); this.extraData.add(null); this.extraData.add(null); - this.extraData.add(null); this.extraData.add(null); this.extraData.add(null); + extraData = new ArrayList<>(StoreContext.MAX_EXTRA_DATA_SIZE); } public List getObjects() { @@ -153,14 +115,6 @@ public List getCertificateMethods() { return Arrays.asList(certLookups); } - public VerifyParameter getVerifyParameter() { - return verifyParameter; - } - - public VerifyFunction getVerifyFunction() { - return verify; - } - /** * c: X509_STORE_set_verify_func */ @@ -168,10 +122,6 @@ public void setVerifyFunction(VerifyFunction func) { verify = func; } - public VerifyCallbackFunction getVerifyCallback() { - return verifyCallback; - } - /** * c: X509_STORE_set_verify_cb_func */ @@ -195,10 +145,13 @@ public void free() throws Exception { /** * c: X509_set_ex_data */ - public int setExtraData(int idx, Object data) { + public void setExtraData(int idx, Object data) { + assert idx >= 0; + assert idx < StoreContext.MAX_EXTRA_DATA_SIZE; synchronized (extraData) { + while (extraData.size() <= idx) extraData.add(null); extraData.set(idx, data); - return 1; + // return 1; } } @@ -207,6 +160,7 @@ public int setExtraData(int idx, Object data) { */ public Object getExtraData(int idx) { synchronized (extraData) { + if (extraData.size() <= idx) return null; return extraData.get(idx); } } @@ -240,11 +194,15 @@ public int setTrust(int trust) { return verifyParameter.setTrust(trust); } + public VerifyParameter getParam() { + return verifyParameter; + } + /** * c: X509_STORE_set1_param */ - public int setParam(VerifyParameter pm) { - return verifyParameter.set(verifyParameter); + public void setParam(VerifyParameter param) { + verifyParameter.set(param); } /** @@ -277,20 +235,20 @@ private static Lookup findLookupMethod(final Lookup[] lookups, final LookupMetho } /** - * c: X509_STORE_add_cert + * int X509_STORE_add_cert(X509_STORE *ctx, X509 *x) */ public int addCertificate(final X509Certificate cert) { if ( cert == null ) return 0; - final Certificate certObj = new Certificate(StoreContext.ensureAux(cert)); + final Certificate obj = new Certificate(StoreContext.ensureAux(cert)); - final X509Object[] objects = this.objects; - if ( matchedObject(objects, certObj) ) { - X509Error.addError(X509_R_CERT_ALREADY_IN_HASH_TABLE); - return 0; + synchronized (this) { + final X509Object[] objects = this.objects; + if ( matchedObject(objects, obj) ) { + return 1; + } + return addObject(obj); // 1 } - - return addObject(certObj, objects.length); } /** @@ -299,15 +257,15 @@ public int addCertificate(final X509Certificate cert) { public int addCRL(final java.security.cert.CRL crl) { if ( crl == null ) return 0; - final CRL crlObj = new CRL(); crlObj.crl = crl; + final CRL obj = new CRL(crl); - final X509Object[] objects = this.objects; - if ( matchedObject(objects, crlObj) ) { - X509Error.addError(X509_R_CERT_ALREADY_IN_HASH_TABLE); - return 0; + synchronized (this) { + final X509Object[] objects = this.objects; + if ( matchedObject(objects, obj) ) { + return 1; + } + return addObject(obj); // 1 } - - return addObject(crlObj, objects.length); } private static boolean matchedObject(final X509Object[] objects, final X509Object xObject) { @@ -317,17 +275,12 @@ private static boolean matchedObject(final X509Object[] objects, final X509Objec return false; } - private synchronized int addObject(final X509Object xObject, final int prevLength) { - final int length = objects.length; - if ( length != prevLength ) { // something added concurrently - if ( matchedObject(objects, xObject) ) { - X509Error.addError(X509_R_CERT_ALREADY_IN_HASH_TABLE); - return 0; - } - } - X509Object[] newObjects = Arrays.copyOf(objects, length + 1); - newObjects[ length ] = xObject; - objects = newObjects; + private int addObject(final X509Object xObject) { + final int len = this.objects.length; + + X509Object[] objects = Arrays.copyOf(this.objects, len + 1); + objects[len] = xObject; + this.objects = objects; return 1; } @@ -365,7 +318,6 @@ public int loadLocations(Ruby runtime, final String file, final String path) thr public int setDefaultPaths(Ruby runtime) throws Exception { Lookup lookup = addLookup(runtime, Lookup.fileLookup()); - //if ( lookup == null ) return 0; try { lookup.loadFile(new CertificateFile.Path(null, X509_FILETYPE_DEFAULT)); @@ -379,11 +331,10 @@ public int setDefaultPaths(Ruby runtime) throws Exception { if (!e.getClass().getSimpleName().equals("NotFound")) { throw e; } - // set_default_paths ignores FileNotFound + OpenSSL.debugStackTrace(runtime, "add X509_CERT_FILER_CTX (to default paths)", e); } lookup = addLookup(runtime, Lookup.hashDirLookup()); - //if ( lookup == null ) return 0; try { lookup.addDir(new CertificateHashDir.Dir(null, X509_FILETYPE_DEFAULT)); @@ -395,7 +346,7 @@ public int setDefaultPaths(Ruby runtime) throws Exception { if (!e.getClass().getSimpleName().equals("NotFound")) { throw e; } - // set_default_paths ignores FileNotFound + OpenSSL.debugStackTrace(runtime, "add X509_HASH_DIR_CTX (to default paths)", e); } X509Error.clearErrors(); @@ -418,7 +369,7 @@ public X509Certificate[] getAcceptedIssuers() { for ( int i = 0; i< objects.length; i++ ) { final X509Object object = objects[i]; if ( object instanceof Certificate ) { - issuers.add( ( (Certificate) object ).x509 ); + issuers.add(( (Certificate) object ).cert); } } return issuers.toArray( new X509Certificate[ issuers.size() ] ); diff --git a/src/main/java/org/jruby/ext/openssl/x509store/StoreContext.java b/src/main/java/org/jruby/ext/openssl/x509store/StoreContext.java index 1e108805..c766172e 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/StoreContext.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/StoreContext.java @@ -27,8 +27,10 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ext.openssl.x509store; +import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; +import java.security.cert.CertificateException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.security.cert.X509Extension; @@ -37,13 +39,19 @@ import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; +import org.jruby.ext.openssl.OpenSSL; import org.jruby.ext.openssl.SecurityHelper; +import org.jruby.util.SafePropertyAccessor; + +import static org.jruby.ext.openssl.x509store.X509Error.addError; +import static org.jruby.ext.openssl.x509store.X509Utils.*; /** * c: X509_STORE_CTX @@ -56,27 +64,20 @@ public class StoreContext { private final Store store; - private int currentMethod; - - X509AuxCertificate certificate; + X509AuxCertificate cert; List untrusted; List crls; - public VerifyParameter verifyParameter; + private VerifyParameter verifyParameter; + private ArrayList extraData; - public List otherContext; + private List otherContext; public StoreContext(final Store store) { this.store = store; } - public static interface CheckPolicyFunction extends Function1 { - public static final CheckPolicyFunction EMPTY = new CheckPolicyFunction(){ - public int call(StoreContext context) { - return -1; - } - }; - } + interface CheckPolicyFunction extends Function1 {} Store.VerifyFunction verify; Store.VerifyCallbackFunction verifyCallback; @@ -89,22 +90,25 @@ public int call(StoreContext context) { CheckPolicyFunction checkPolicy; Store.CleanupFunction cleanup; + Store.LookupCerts lookup_certs; + public boolean isValid; - public int lastUntrusted; - public List chain; //List - public PolicyTree tree; + private int num_untrusted; // last_untrusted (OpenSSL 1.0.2) in the chain + + private ArrayList chain; - public int explicitPolicy; + private PolicyTree tree; + private int explicit_policy; - public int error; - public int errorDepth; + int error; + int error_depth; - X509AuxCertificate currentCertificate; - X509AuxCertificate currentIssuer; - X509CRL currentCRL; + X509AuxCertificate current_cert; + X509AuxCertificate current_issuer; + X509CRL current_crl; - List extraData; + private StoreContext parent; // NOTE: not implemented - dummy null for now public Store getStore() { return store; @@ -131,10 +135,79 @@ public Object getApplicationData() { return getExtraData(0); } - /** - * c: X509_STORE_CTX_get1_issuer + /*- + * Try to get issuer certificate from store. Due to limitations + * of the API this can only retrieve a single certificate matching + * a given subject name. However it will fill the cache with all + * matching certificates, so we can examine the cache for all + * matches. + * + * Return values are: + * 1 lookup successful. + * 0 certificate not found. + * -1 some other error. + * + * int X509_STORE_CTX_get1_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x) */ - int getFirstIssuer(final X509AuxCertificate[] issuers, final X509AuxCertificate x) throws Exception { + int getFirstIssuer(final X509AuxCertificate[] _issuer, final X509AuxCertificate x) throws Exception { + int ok; + // _issuer[0] = null; + final Name xn = new Name( x.getIssuerX500Principal() ); + final X509Object[] s_obj = new X509Object[1]; + ok = store == null ? 0 : getBySubject(X509_LU_X509, xn, s_obj); + if (ok != 1) { + if ( ok == X509Utils.X509_LU_RETRY ) { + X509Error.addError(X509Utils.X509_R_SHOULD_RETRY); + return -1; + } + else if ( ok != X509Utils.X509_LU_FAIL ) { + return -1; + } + return 0; + } + /* If certificate matches all OK */ + X509Object obj = s_obj[0]; + if ( checkIssued.call(this, x, ((Certificate) obj).cert) != 0 ) { + X509AuxCertificate issuer = ((Certificate) obj).cert; + if (x509_check_cert_time(issuer, -1)) { + _issuer[0] = issuer; + return 1; + } + } + + List objects = store.getObjects(); + int idx = X509Object.indexBySubject(objects, X509Utils.X509_LU_X509, xn); + if ( idx == -1 ) return 0; + + int ret = 0; + /* Look through all matching certificates for a suitable issuer */ + for ( int i = idx; i < objects.size(); i++ ) { + final X509Object pobj = objects.get(i); + /* See if we've run past the matches */ + if (pobj.type() != X509_LU_X509) { + break; // return 0 + } + final X509AuxCertificate x509 = ((Certificate) pobj).cert; + if ( ! xn.equalTo( x509.getSubjectX500Principal() ) ) { + break; // return 0 + } + if ( checkIssued.call(this, x, x509) != 0 ) { + _issuer[0] = x509; + ret = 1; + /* + * If times check, exit with match, otherwise keep looking. + * Leave last match in issuer so we return nearest + * match if no certificate time is OK. + */ + if (x509_check_cert_time(x509, -1)) break; // return 1; + } + } + return ret; + } + + // NOTE: not based on OpenSSL - self invented (till JOSSL 1.1.1 port) + private int getValidIssuers(final X509AuxCertificate x, final List _issuers) + throws Exception { final Name xn = new Name( x.getIssuerX500Principal() ); final X509Object[] s_obj = new X509Object[1]; int ok = store == null ? 0 : getBySubject(X509Utils.X509_LU_X509, xn, s_obj); @@ -148,33 +221,41 @@ else if ( ok != X509Utils.X509_LU_FAIL ) { } return 0; } - + int ret = 0; + /* If certificate matches all OK */ X509Object obj = s_obj[0]; - if ( checkIssued.call(this, x, ((Certificate) obj).x509) != 0 ) { - issuers[0] = ((Certificate) obj).x509; - return 1; + if ( checkIssued.call(this, x, ((Certificate) obj).cert) != 0 ) { + X509AuxCertificate issuer = ((Certificate) obj).cert; + if (x509_check_cert_time(issuer, -1)) { + _issuers.add(issuer); + ret = 1; + } } List objects = store.getObjects(); + int idx = X509Object.indexBySubject(objects, X509Utils.X509_LU_X509, xn); - if ( idx == -1 ) return 0; + if ( idx == -1 ) return ret; /* Look through all matching certificates for a suitable issuer */ for ( int i = idx; i < objects.size(); i++ ) { final X509Object pobj = objects.get(i); if ( pobj.type() != X509Utils.X509_LU_X509 ) { - return 0; + continue; } - final X509AuxCertificate x509 = ((Certificate) pobj).x509; + final X509AuxCertificate x509 = ((Certificate) pobj).cert; if ( ! xn.equalTo( x509.getSubjectX500Principal() ) ) { - return 0; + continue; } + if ( checkIssued.call(this, x, x509) != 0 ) { - issuers[0] = x509; - return 1; + if (x509_check_cert_time(x509, -1)) { + _issuers.add(x509); + ret = 1; + } } } - return 0; + return ret; } public static List ensureAux(final Collection input) { @@ -203,88 +284,111 @@ public static X509AuxCertificate ensureAux(final X509Certificate input) { } /** - * c: X509_STORE_CTX_init + * int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, X509 *cert, + * STACK_OF(X509) *chain) + * + * @param cert the certificate (to be verified) + * @param untrusted_chain the (untrusted) chain of certs returned by the server + * @return 1 */ - public int init(X509AuxCertificate cert, List chain) { - int ret = 1; - this.currentMethod = 0; - this.certificate = cert; - this.untrusted = chain; + public int init(X509AuxCertificate cert, List untrusted_chain) { + // int ret = 1; + this.cert = cert; + this.untrusted = untrusted_chain; this.crls = null; - this.lastUntrusted = 0; + this.num_untrusted = 0; this.otherContext = null; this.isValid = false; this.chain = null; - this.error = 0; - this.explicitPolicy = 0; - this.errorDepth = 0; - this.currentCertificate = null; - this.currentIssuer = null; + this.error = V_OK; + this.explicit_policy = 0; + this.error_depth = 0; + this.current_cert = null; + this.current_issuer = null; + this.current_crl = null; this.tree = null; + this.parent = null; - this.verifyParameter = new VerifyParameter(); - - if ( store != null ) { - ret = verifyParameter.inherit(store.verifyParameter); - } else { - verifyParameter.flags |= X509Utils.X509_VP_FLAG_DEFAULT | X509Utils.X509_VP_FLAG_ONCE; - } - + /* store->cleanup is always 0 in OpenSSL, if set must be idempotent */ if ( store != null ) { - verifyCallback = store.getVerifyCallback(); - cleanup = store.cleanup; + this.cleanup = store.cleanup; } else { - cleanup = Store.CleanupFunction.EMPTY; - } - - if ( ret != 0 ) { - ret = verifyParameter.inherit(VerifyParameter.lookup("default")); - } - - if ( ret == 0 ) { - X509Error.addError(X509Utils.ERR_R_MALLOC_FAILURE); - return 0; + this.cleanup = null; } - this.checkIssued = defaultCheckIssued; + this.checkIssued = VERIFY_LEGACY ? check_issued_legacy : check_issued; this.getIssuer = getFirstIssuer; this.verifyCallback = nullCallback; - this.verify = internalVerify; - this.checkRevocation = defaultCheckRevocation; + this.verify = null; + this.checkRevocation = StoreContext.check_revocation; this.getCRL = defaultGetCRL; - this.checkCRL = defaultCheckCRL; + this.checkCRL = check_crl_legacy; this.certificateCRL = defaultCertificateCRL; if ( store != null ) { - if ( store.checkIssued != null && store.checkIssued != Store.CheckIssuedFunction.EMPTY ) { + if ( store.checkIssued != null ) { this.checkIssued = store.checkIssued; } - if ( store.getIssuer != null && store.getIssuer != Store.GetIssuerFunction.EMPTY ) { + if ( store.getIssuer != null ) { this.getIssuer = store.getIssuer; } - if ( store.verifyCallback != null && store.verifyCallback != Store.VerifyCallbackFunction.EMPTY ) { + if ( store.verifyCallback != null ) { this.verifyCallback = store.verifyCallback; } - if ( store.verify != null && store.verify != Store.VerifyFunction.EMPTY) { + if ( store.verify != null ) { this.verify = store.verify; } - if ( store.checkRevocation != null && store.checkRevocation != Store.CheckRevocationFunction.EMPTY) { + if ( store.checkRevocation != null ) { this.checkRevocation = store.checkRevocation; } - if ( store.getCRL != null && store.getCRL != Store.GetCRLFunction.EMPTY) { + if ( store.getCRL != null ) { this.getCRL = store.getCRL; } - if( store.checkCRL != null && store.checkCRL != Store.CheckCRLFunction.EMPTY) { + if( store.checkCRL != null ) { this.checkCRL = store.checkCRL; } - if ( store.certificateCRL != null && store.certificateCRL != Store.CertificateCRLFunction.EMPTY) { + if ( store.certificateCRL != null ) { this.certificateCRL = store.certificateCRL; } } - this.checkPolicy = defaultCheckPolicy; + if (store != null && store.lookup_certs != null) { + this.lookup_certs = store.lookup_certs; + } else { + this.lookup_certs = new Store.LookupCerts() { + public List call(StoreContext ctx, Name name) throws Exception { + return ctx.get1_certs(name); + } + }; + } + + // store->check_policy + this.checkPolicy = StoreContext.check_policy; + + this.verifyParameter = new VerifyParameter(); + + if ( store != null ) { + verifyParameter.inherit(store.verifyParameter); + } else { + verifyParameter.inheritFlags |= X509_VP_FLAG_DEFAULT | X509_VP_FLAG_ONCE; + } + + verifyParameter.inherit(VerifyParameter.lookup("default")); + + /* + * XXX: For now, continue to inherit trust from VPM, but infer from the + * purpose if this still yields the default value. + */ + if (verifyParameter.trust == X509_TRUST_DEFAULT) { + int idx = Purpose.getByID(verifyParameter.purpose); + Purpose xp = Purpose.getFirst(idx); - // getExtraData(); + if (xp != null) { + verifyParameter.trust = xp.trust; // X509_PURPOSE_get_trust + } + } + + // getExtraData(); // CRYPTO_new_ex_data return 1; } @@ -300,7 +404,7 @@ public void trustedStack(List sk) { * c: X509_STORE_CTX_cleanup */ public void cleanup() throws Exception { - if (cleanup != null && cleanup != Store.CleanupFunction.EMPTY) { + if (cleanup != null) { cleanup.call(this); } verifyParameter = null; @@ -309,39 +413,41 @@ public void cleanup() throws Exception { extraData = null; } + // NOTE: 0 is reserved for getApplicationData() (X509_STORE_CTX_get_app_data) + /** - * c: find_issuer + * index for @verify_callback in ex_data */ - public X509AuxCertificate findIssuer(final List certs, final X509AuxCertificate cert) throws Exception { - for ( X509AuxCertificate issuer : certs ) { - if ( checkIssued.call(this, cert, issuer) != 0 ) { - return issuer; - } - } - return null; - } + public static final int ossl_ssl_ex_vcb_idx = 1; + /** + * index for holding the SSLContext instance in ex_data + */ + public static final int ossl_ssl_ex_ptr_idx = 2; // TODO needs impl - public List getExtraData() { - if ( this.extraData != null ) return this.extraData; - ArrayList extraData = new ArrayList(8); - extraData.add(null); extraData.add(null); extraData.add(null); - extraData.add(null); extraData.add(null); extraData.add(null); - return this.extraData = extraData; - } + static final int MAX_EXTRA_DATA_SIZE = 4; /** * c: X509_STORE_CTX_set_ex_data */ - public int setExtraData(int idx, Object data) { - getExtraData().set(idx, data); - return 1; + public final void setExtraData(final int idx, final Object data) { + if (extraData == null) { + if (data == null) return; + extraData = new ArrayList<>(MAX_EXTRA_DATA_SIZE); + } else { + extraData.ensureCapacity(idx + 1); + } + while (extraData.size() <= idx) extraData.add(null); + extraData.set(idx, data); + // return 1; } /** * c: X509_STORE_CTX_get_ex_data */ - public Object getExtraData(int idx) { - return getExtraData().get(idx); + public final Object getExtraData(final int idx) { + if (extraData == null) return null; + if (extraData.size() < idx) return null; + return extraData.get(idx); } /** @@ -362,18 +468,18 @@ public void setError(int s) { * c: X509_STORE_CTX_get_error_depth */ public int getErrorDepth() { - return errorDepth; + return error_depth; } /** * c: X509_STORE_CTX_get_current_cert */ public X509AuxCertificate getCurrentCertificate() { - return currentCertificate; + return current_cert; } public X509CRL getCurrentCRL() { - return currentCRL; + return current_crl; } /** @@ -395,11 +501,11 @@ public List getFirstChain() { * c: X509_STORE_CTX_set_cert */ public void setCertificate(X509AuxCertificate x) { - this.certificate = x; + this.cert = x; } public void setCertificate(X509Certificate x) { - this.certificate = ensureAux(x); + this.cert = ensureAux(x); } /** @@ -421,14 +527,16 @@ public void setCRLs(List sk) { } /** - * c: X509_STORE_CTX_set_purpose + * int X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose) + * @return 0 || 1 */ public int setPurpose(int purpose) { return purposeInherit(0, purpose, 0); } /** - * c: X509_STORE_CTX_set_trust + * int X509_STORE_CTX_set_trust(X509_STORE_CTX *ctx, int trust) + * @return 0 || 1 */ public int setTrust(int trust) { return purposeInherit(0, 0, trust); @@ -502,46 +610,50 @@ public int loadVerifyLocations(Ruby runtime, String CAfile, String CApath) { } } */ - /** - * c: X509_STORE_CTX_purpose_inherit + /* + * int X509_STORE_CTX_purpose_inherit(X509_STORE_CTX *ctx, int def_purpose, + * int purpose, int trust) */ - public int purposeInherit(int defaultPurpose,int purpose, int trust) { + private int purposeInherit(final int def_purpose, int purpose, int trust) { int idx; - if(purpose == 0) { - purpose = defaultPurpose; + /* If purpose not set use default */ + if (purpose == 0) { + purpose = def_purpose; } - if(purpose != 0) { - idx = Purpose.getByID(purpose); - if(idx == -1) { + /* If we have a purpose then check it is valid */ + if (purpose != 0) { + idx = Purpose.getByID(purpose); // X509_PURPOSE_get_by_id + if (idx == -1) { X509Error.addError(X509Utils.X509_R_UNKNOWN_PURPOSE_ID); return 0; } - Purpose ptmp = Purpose.getFirst(idx); - if(ptmp.trust == X509Utils.X509_TRUST_DEFAULT) { - idx = Purpose.getByID(defaultPurpose); - if(idx == -1) { + Purpose ptmp = Purpose.getFirst(idx); // X509_PURPOSE_get0 + if (ptmp.trust == X509Utils.X509_TRUST_DEFAULT) { + idx = Purpose.getByID(def_purpose); + if (idx == -1) { X509Error.addError(X509Utils.X509_R_UNKNOWN_PURPOSE_ID); return 0; } - ptmp = Purpose.getFirst(idx); + ptmp = Purpose.getFirst(idx); // X509_PURPOSE_get0 } - if(trust == 0) { + /* If trust not set then get from purpose default */ + if (trust == 0) { trust = ptmp.trust; } } - if(trust != 0) { - idx = Trust.getByID(trust); - if(idx == -1) { + if (trust != 0) { + idx = Trust.getByID(trust); // X509_TRUST_get_by_id + if (idx == -1) { X509Error.addError(X509Utils.X509_R_UNKNOWN_TRUST_ID); return 0; } } - if(purpose != 0 && verifyParameter.purpose == 0) { - verifyParameter.purpose = purpose; + if (purpose != 0 && getParam().purpose == 0) { + getParam().purpose = purpose; } - if(trust != 0 && verifyParameter.trust == 0) { - verifyParameter.trust = trust; + if (trust != 0 && getParam().trust == 0) { + getParam().trust = trust; } return 1; } @@ -578,7 +690,7 @@ PolicyTree getPolicyTree() { * c: X509_STORE_CTX_get_explicit_policy */ public int getExplicitPolicy() { - return explicitPolicy; + return explicit_policy; } /** @@ -598,70 +710,157 @@ public void setParam(VerifyParameter param) { /** * c: X509_STORE_CTX_set_default */ - public int setDefault(String name) { + public void setDefault(String name) { VerifyParameter p = VerifyParameter.lookup(name); - if ( p == null ) return 0; - return verifyParameter.inherit(p); + if ( p == null ) return; // return 0 + verifyParameter.inherit(p); // return 1 } - /** - * c: X509_STORE_get_by_subject (it gets X509_STORE_CTX as the first parameter) + /* + * int X509_STORE_CTX_get_by_subject(X509_STORE_CTX *vs, X509_LOOKUP_TYPE type, + * X509_NAME *name, X509_OBJECT *ret) */ - public int getBySubject(int type,Name name,X509Object[] ret) throws Exception { - Store c = store; - - X509Object tmp = X509Object.retrieveBySubject(c.getObjects(),type,name); - if ( tmp == null ) { - List certificateMethods = c.getCertificateMethods(); - for(int i=currentMethod; i 0 ) { + int j = lu.bySubject(type, name, stmp); + if (j != 0) { tmp = stmp[0]; break; } } - currentMethod = 0; - - if ( tmp == null ) return 0; + if (tmp == null) return 0; } ret[0] = tmp; return 1; } - /** - * c: X509_verify_cert + /* + * STACK_OF(X509) *X509_STORE_CTX_get1_certs(X509_STORE_CTX *ctx, X509_NAME *nm) */ - public int verifyCertificate() throws Exception { - X509AuxCertificate x, xtmp = null, chain_ss = null; - //X509_NAME xn; - int bad_chain = 0, depth, i, num; + List get1_certs(final Name nm) throws Exception { + if (store == null) return null; + + // NOTE: very rough draft that resembles OpenSSL bits + + List sk = matchCachedCertObjectsFromStore(nm); + + if (sk.isEmpty()) { + /* + * Nothing found in cache: do lookup to possibly add new objects to cache + */ + boolean found = false; + for (Lookup lu : store.getCertificateMethods()) { + X509Object[] stmp = new X509Object[1]; + if (lu.bySubject(X509_LU_X509, nm, stmp) != 0) found = true; + } + if (!found) return sk; + } + + sk = matchCachedCertObjectsFromStore(nm); + return sk; + } + + /* Get issuer, without duplicate suppression */ + private int get_issuer(X509AuxCertificate[] issuer, X509AuxCertificate cert) throws Exception { + final ArrayList saved_chain = this.chain; + int ok; + + this.chain = null; + try { + ok = this.getIssuer.call(this, issuer, cert); + } finally { + this.chain = saved_chain; + } + return ok; + } + + private List matchCachedCertObjectsFromStore(final Name name) { + ArrayList sk = new ArrayList(); + for (X509Object obj : store.getObjects()) { + if (obj.type() == X509_LU_X509 && obj.isName(name)) { + sk.add(((Certificate) obj).cert); + } + } + return sk; + } - if ( certificate == null ) { - X509Error.addError(X509Utils.X509_R_NO_CERT_SET_FOR_US_TO_VERIFY); + /* + * c: int X509_verify_cert(X509_STORE_CTX *ctx) + */ + public int verifyCertificate() throws Exception { + if (cert == null) { + addError(X509_R_NO_CERT_SET_FOR_US_TO_VERIFY); + this.error = V_ERR_INVALID_CALL; return -1; } - // first we make sure the chain we are going to build is - // present and that the first entry is in place + if (chain != null) { + /* + * This X509_STORE_CTX has already been used to verify a cert. We cannot do another one. + */ + addError(ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); + this.error = V_ERR_INVALID_CALL; + return -1; + } - if ( chain == null ) { - chain = new ArrayList(); - chain.add(certificate); - lastUntrusted = 1; + /* + * first we make sure the chain we are going to build is present and that + * the first entry is in place + */ + //if (chain == null) { + chain = new ArrayList(8); + chain.add(cert); + num_untrusted = 1; + //} + + // NOTE: NOT IMPLEMENTED + /* If the peer's public key is too weak, we can stop early. */ + + int ret = verifyChain(); + + /* + * Safety-net. If we are returning an error, we must also set ctx->error, + * so that the chain is not considered verified should the error be ignored + * (e.g. TLS with SSL_VERIFY_NONE). + */ + if (ret <= 0 && this.error == V_OK) { + this.error = V_ERR_UNSPECIFIED; } + return ret; + } + + private static final boolean VERIFY_LEGACY; + static { + String verify = SafePropertyAccessor.getProperty("jruby.openssl.x509.store.verify"); + VERIFY_LEGACY = "legacy".equals(verify); + } + + private int verifyChain() throws Exception { + if (VERIFY_LEGACY) return verify_chain_legacy(); + return verify_chain(); + } + + /* + @ @note: based on pre OpenSSL 1.0 code + * + * c: static int verify_chain(X509_STORE_CTX *ctx) + */ + @SuppressWarnings("deprecation") + int verify_chain_legacy() throws Exception { + X509AuxCertificate x, xtmp = null, chain_ss = null; + int bad_chain = 0, depth, i, num; // We use a temporary STACK so we can chop and hack at it - List sktmp = null; - if ( untrusted != null ) { - sktmp = new ArrayList(untrusted); - } + LinkedList sktmp = untrusted != null ? new LinkedList<>(untrusted) : null; + num = chain.size(); x = chain.get(num - 1); depth = verifyParameter.depth; @@ -671,11 +870,11 @@ public int verifyCertificate() throws Exception { if ( checkIssued.call(this, x, x) != 0 ) break; if ( sktmp != null ) { - xtmp = findIssuer(sktmp, x); + xtmp = findIssuer(sktmp, x, true); if ( xtmp != null ) { chain.add(xtmp); sktmp.remove(xtmp); - lastUntrusted++; + num_untrusted++; x = xtmp; num++; continue; @@ -704,8 +903,8 @@ public int verifyCertificate() throws Exception { xtmp = p_xtmp[0]; if ( ok <= 0 || ! x.equals(xtmp) ) { error = X509Utils.V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT; - currentCertificate = x; - errorDepth = i-1; + current_cert = x; + error_depth = i-1; bad_chain = 1; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; @@ -714,21 +913,21 @@ public int verifyCertificate() throws Exception { // so we get any trust settings. x = xtmp; chain.set(i-1,x); - lastUntrusted = 0; + num_untrusted = 0; } } else { // extract and save self signed certificate for later use chain_ss = chain.remove(chain.size()-1); - lastUntrusted--; + num_untrusted--; num--; x = chain.get(num-1); } } + // We now lookup certs from the certificate store for(;;) { // If we have enough, we break if ( depth < num ) break; - //xn = new X509_NAME(x.getIssuerX500Principal()); // If we are self signed, we break if ( checkIssued.call(this, x, x) != 0 ) break; @@ -740,30 +939,30 @@ public int verifyCertificate() throws Exception { if ( ok == 0 ) break; x = xtmp; + chain.add(x); num++; } /* we now have our chain, lets check it... */ - //xn = new X509_NAME(x.getIssuerX500Principal()); /* Is last certificate looked up self signed? */ if ( checkIssued.call(this, x, x) == 0 ) { if ( chain_ss == null || checkIssued.call(this, x, chain_ss) == 0 ) { - if(lastUntrusted >= num) { + if (num_untrusted >= num) { error = X509Utils.V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; } else { error = X509Utils.V_ERR_UNABLE_TO_GET_ISSUER_CERT; } - currentCertificate = x; + current_cert = x; } else { chain.add(chain_ss); num++; - lastUntrusted = num; - currentCertificate = chain_ss; + num_untrusted = num; + current_cert = chain_ss; error = X509Utils.V_ERR_SELF_SIGNED_CERT_IN_CHAIN; } - errorDepth = num - 1; + error_depth = num - 1; bad_chain = 1; int ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; @@ -785,7 +984,7 @@ public int verifyCertificate() throws Exception { if ( ok == 0 ) return ok; /* At this point, we have a chain and need to verify it */ - if ( verify != null && verify != Store.VerifyFunction.EMPTY ) { + if ( verify != null ) { ok = verify.call(this); } else { ok = internalVerify.call(this); @@ -801,6 +1000,364 @@ public int verifyCertificate() throws Exception { return ok; } + /* + @ @note: based OpenSSL 1.1.1 + * + * c: static int verify_chain(X509_STORE_CTX *ctx) + */ + int verify_chain() throws Exception { + int err; + int ok; + + /* + * Before either returning with an error, or continuing with CRL checks, + * instantiate chain public key parameters. + */ + if ((ok = build_chain()) == 0 || + (ok = check_chain_extensions()) == 0 || + //(ok = check_auth_level(ctx)) == 0 || + //(ok = check_id()) == 0 || + true); + if (ok == 0 || (ok = checkRevocation.call(this)) == 0) + return ok; + + //err = X509_chain_check_suiteb(&ctx->error_depth, NULL, ctx->chain, ctx->param->flags); + //if (err != V_OK) { + // if ((ok = verify_cb_cert(null, this.errorDepth, err)) == 0) + // return ok; + //} + + /* Verify chain signatures and expiration times */ + ok = verify != null ? verify.call(this) : internal_verify(); + if (ok == 0) return ok; + + //if ((ok = check_name_constraints(ctx)) == 0) + // return ok; + + /* If we get this far evaluate policies */ + if ((getParam().flags & V_FLAG_POLICY_CHECK) != 0) { + ok = checkPolicy.call(this); + } + return ok; + } + + private static final short S_DOUNTRUSTED = (1 << 0); /* Search untrusted chain */ + private static final short S_DOTRUSTED = (1 << 1); /* Search trusted store */ + private static final short S_DOALTERNATE = (1 << 2); /* Retry with pruned alternate chain */ + + /* + * x509_vfy.c: static int build_chain(X509_STORE_CTX *ctx) + */ + int build_chain() throws Exception { + int num = chain.size(); + X509AuxCertificate cert = chain.get(num - 1); + boolean ss = cert_self_signed(cert); + short search; + boolean may_trusted = false; + boolean may_alternate = false; + int trust = X509_TRUST_UNTRUSTED; + int alt_untrusted = 0; + int depth; + int ok; + + /* Our chain starts with a single untrusted element. */ + assert num == 1 && num_untrusted == num; + + /* + * Set up search policy, untrusted if possible, trusted-first if enabled. + * If we're doing DANE and not doing PKIX-TA/PKIX-EE, we never look in the + * trust_store, otherwise we might look there first. If not trusted-first, + * and alternate chains are not disabled, try building an alternate chain + * if no luck with untrusted first. + */ + search = untrusted != null ? S_DOUNTRUSTED : 0; + //if (DANETLS_HAS_PKIX(dane) || !DANETLS_HAS_DANE(dane)) { + if (search == 0 || (getParam().flags & V_FLAG_TRUSTED_FIRST) != 0) { + search |= S_DOTRUSTED; + } else if ((getParam().flags & V_FLAG_NO_ALT_CHAINS) == 0) { + may_alternate = true; + } + may_trusted = true; + //} + + /* + * Shallow-copy the stack of untrusted certificates (with TLS, this is + * typically the content of the peer's certificate message) so can make + * multiple passes over it, while free to remove elements as we go. + */ + LinkedList sktmp = untrusted != null ? new LinkedList<>(untrusted) : null; + + depth = getParam().depth; + + /* + * Still absurdly large, but arithmetically safe, a lower hard upper bound + * might be reasonable. + */ + if (depth > Integer.MAX_VALUE / 2) depth = Integer.MAX_VALUE / 2; + + /* + * Try to Extend the chain until we reach an ultimately trusted issuer. + * Build chains up to one longer the limit, later fail if we hit the limit, + * with an X509_V_ERR_CERT_CHAIN_TOO_LONG error code. + */ + depth = depth + 1; + + while (search != 0) { + X509AuxCertificate x, xtmp = null; + + /* + * Look in the trust store if enabled for first lookup, or we've run + * out of untrusted issuers and search here is not disabled. When we + * reach the depth limit, we stop extending the chain, if by that point + * we've not found a trust-anchor, any trusted chain would be too long. + * + * The error reported to the application verify callback is at the + * maximal valid depth with the current certificate equal to the last + * not ultimately-trusted issuer. For example, with verify_depth = 0, + * the callback will report errors at depth=1 when the immediate issuer + * of the leaf certificate is not a trust anchor. No attempt will be + * made to locate an issuer for that certificate, since such a chain + * would be a-priori too long. + */ + if ((search & S_DOTRUSTED) != 0) { + num = chain.size(); int i = num; + if ((search & S_DOALTERNATE) != 0) { + /* + * As high up the chain as we can, look for an alternative + * trusted issuer of an untrusted certificate that currently + * has an untrusted issuer. We use the alt_untrusted variable + * to track how far up the chain we find the first match. It + * is only if and when we find a match, that we prune the chain + * and reset ctx->num_untrusted to the reduced count of + * untrusted certificates. While we're searching for such a + * match (which may never be found), it is neither safe nor + * wise to preemptively modify either the chain or + * ctx->num_untrusted. + * + * Note, like ctx->num_untrusted, alt_untrusted is a count of + * untrusted certificates, not a "depth". + */ + i = alt_untrusted; + } + x = chain.get(i - 1); + + X509AuxCertificate[] p_xtmp = new X509AuxCertificate[] { xtmp }; + ok = (depth < num) ? 0 : getIssuer.call(this, p_xtmp, x); // get_issuer(&xtmp, ctx, x) + xtmp = p_xtmp[0]; + + if (ok < 0) { + trust = X509_TRUST_REJECTED; + this.error = V_ERR_STORE_LOOKUP; + search = 0; + continue; + } + + if (ok > 0) { + /* + * Alternative trusted issuer for a mid-chain untrusted cert? + * Pop the untrusted cert's successors and retry. We might now + * be able to complete a valid chain via the trust store. Note + * that despite the current trust-store match we might still + * fail complete the chain to a suitable trust-anchor, in which + * case we may prune some more untrusted certificates and try + * again. Thus the S_DOALTERNATE bit may yet be turned on + * again with an even shorter untrusted chain! + * + * We might find a suitable trusted certificate among the ones from the trust store. + */ + if ((search & S_DOALTERNATE) != 0) { + if (!(num > i && i > 0 && ss == false)) { // ossl_assert + OpenSSL.debug(this + " assert failure (num > i && i > 0 && ss == false)"); + addError(ERR_R_INTERNAL_ERROR); + trust = X509_TRUST_REJECTED; + this.error = V_ERR_UNSPECIFIED; + search = 0; + continue; + } + search &= ~S_DOALTERNATE; + for (; num > i; --num) chain.remove(chain.size() - 1); // pop + num_untrusted = num; + } + + /* + * Self-signed untrusted certificates get replaced by their + * trusted matching issuer. Otherwise, grow the chain. + */ + if (ss == false) { + chain.add(x = xtmp); + ss = cert_self_signed(x); + } else if (num == num_untrusted) { + /* + * We have a self-signed certificate that has the same + * subject name (and perhaps keyid and/or serial number) as + * a trust-anchor. We must have an exact match to avoid + * possible impersonation via key substitution etc. + */ + if (!x.equals(xtmp)) { + /* Self-signed untrusted mimic. */ + ok = 0; + } else { + num_untrusted = --num; + chain.set(num, x = xtmp); + } + } + + /* + * We've added a new trusted certificate to the chain, recheck + * trust. If not done, and not self-signed look deeper. + * Whether or not we're doing "trusted first", we no longer + * look for untrusted certificates from the peer's chain. + * + * At this point ctx->num_trusted and num must reflect the + * correct number of untrusted certificates, since the DANE + * logic in check_trust() depends on distinguishing CAs from + * "the wire" from CAs from the trust store. In particular, the + * certificate at depth "num" should be the new trusted + * certificate with ctx->num_untrusted <= num. + */ + if (ok != 0) { + if (!(num_untrusted <= num)) { // ossl_assert + OpenSSL.debug(this + " assert failure (num_untrusted <= num)"); + addError(ERR_R_INTERNAL_ERROR); + trust = X509_TRUST_REJECTED; + this.error = V_ERR_UNSPECIFIED; + search = 0; + continue; + } + search &= ~S_DOUNTRUSTED; + switch (trust = check_trust(num)) { + case X509_TRUST_TRUSTED: + case X509_TRUST_REJECTED: + search = 0; + continue; + } + if (ss == false) continue; + } + } + + /* + * No dispositive decision, and either self-signed or no match, if + * we were doing untrusted-first, and alt-chains are not disabled, + * do that, by repeatedly losing one untrusted element at a time, + * and trying to extend the shorted chain. + */ + if ((search & S_DOUNTRUSTED) == 0) { + /* Continue search for a trusted issuer of a shorter chain? */ + if ((search & S_DOALTERNATE) != 0 && --alt_untrusted > 0) + continue; + /* Still no luck and no fallbacks left? */ + if (!may_alternate || (search & S_DOALTERNATE) != 0 || num_untrusted < 2) + break; + /* Search for a trusted issuer of a shorter chain */ + search |= S_DOALTERNATE; + alt_untrusted = num_untrusted - 1; + ss = false; + } + } + + /* + * Extend chain with peer-provided certificates + */ + if ((search & S_DOUNTRUSTED) != 0) { + num = chain.size(); + if (!(num == num_untrusted)) { // ossl_assert + OpenSSL.debug(this + " assert failure (num == num_untrusted)"); + addError(ERR_R_INTERNAL_ERROR); + trust = X509_TRUST_REJECTED; + this.error = V_ERR_UNSPECIFIED; + search = 0; + continue; + } + x = chain.get(num-1); + + /* + * Once we run out of untrusted issuers, we stop looking for more + * and start looking only in the trust store if enabled. + */ + xtmp = (ss || depth < num) ? null : find_issuer(sktmp, x); + if (xtmp == null) { + search &= ~S_DOUNTRUSTED; + if (may_trusted) search |= S_DOTRUSTED; + continue; + } + + /* Drop this issuer from future consideration */ + sktmp.remove(xtmp); + + chain.add(xtmp); + + x = xtmp; + ++num_untrusted; + ss = cert_self_signed(xtmp); + + trust = X509_TRUST_UNTRUSTED; // switch (trust = check_dane_issuer(...)) + } + } + // sk_X509_free(sktmp) + + /* + * Last chance to make a trusted chain, either bare DANE-TA public-key + * signers, or else direct leaf PKIX trust. + */ + num = chain.size(); + if (num <= depth) { + if (trust == X509_TRUST_UNTRUSTED && num == num_untrusted) { + trust = check_trust(num); + } + } + + switch (trust) { + case X509_TRUST_TRUSTED: + return 1; + case X509_TRUST_REJECTED: + /* Callback already issued */ + return 0; + case X509_TRUST_UNTRUSTED: + default: + num = chain.size(); + if (num > depth) { + return verify_cb_cert(null, num - 1, V_ERR_CERT_CHAIN_TOO_LONG); + } + if (ss && num == 1) { + return verify_cb_cert(null, num - 1, V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT); + } + if (ss) { + return verify_cb_cert(null, num - 1, V_ERR_SELF_SIGNED_CERT_IN_CHAIN); + } + if (num_untrusted < num) { + return verify_cb_cert(null, num - 1, V_ERR_UNABLE_TO_GET_ISSUER_CERT); + } + return verify_cb_cert(null, num - 1, V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY); + } + } + + @Deprecated // legacy find_issuer + private X509AuxCertificate findIssuer(final List certs, + final X509AuxCertificate cert, final boolean check_time) throws Exception { + for ( X509AuxCertificate issuer : certs ) { + if ( checkIssued.call(this, cert, issuer) != 0 ) { + if (!check_time || x509_check_cert_time(issuer, -1)) return issuer; + } + } + return null; + } + + /* + * Given a STACK_OF(X509) find the issuer of cert (if any) + * + * x509_vfy.c: static int build_chain(X509_STORE_CTX *ctx) + */ + private X509AuxCertificate find_issuer(List sk, X509AuxCertificate x) throws Exception { + X509AuxCertificate rv = null; + + for (X509AuxCertificate issuer : sk) { + if (checkIssued.call(this, x, issuer) != 0) { + rv = issuer; + if (x509_check_cert_time(rv, -1)) break; + } + } + return rv; + } private final static Set CRITICAL_EXTENSIONS = new HashSet(8); static { @@ -845,20 +1402,20 @@ public int checkChainExtensions() throws Exception { } catch (SecurityException e) { /* ignore if we can't use System.getenv */ } - for ( int i = 0; i < lastUntrusted; i++ ) { + for ( int i = 0; i < num_untrusted; i++ ) { // lastUntrusted int ret; x = chain.get(i); if ( (verifyParameter.flags & X509Utils.V_FLAG_IGNORE_CRITICAL) == 0 && unhandledCritical(x) ) { error = X509Utils.V_ERR_UNHANDLED_CRITICAL_EXTENSION; - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; } if ( allow_proxy_certs == 0 && x.getExtensionValue("1.3.6.1.5.5.7.1.14") != null ) { error = X509Utils.V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED; - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; } @@ -891,8 +1448,8 @@ public int checkChainExtensions() throws Exception { break; } if(ret == 0) { - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; } @@ -900,8 +1457,8 @@ public int checkChainExtensions() throws Exception { ret = Purpose.checkPurpose(x,verifyParameter.purpose, must_be_ca > 0 ? 1 : 0); if(ret == 0 || ((verifyParameter.flags & X509Utils.V_FLAG_X509_STRICT) != 0 && ret != 1)) { error = X509Utils.V_ERR_INVALID_PURPOSE; - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if(ok == 0) { return ok; @@ -911,8 +1468,8 @@ public int checkChainExtensions() throws Exception { if(i > 1 && x.getBasicConstraints() != -1 && x.getBasicConstraints() != Integer.MAX_VALUE && (i > (x.getBasicConstraints() + proxy_path_length + 1))) { error = X509Utils.V_ERR_PATH_LENGTH_EXCEEDED; - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; } @@ -923,8 +1480,8 @@ public int checkChainExtensions() throws Exception { int pcpathlen = ((ASN1Integer)pci.getObjectAt(0)).getValue().intValue(); if(i > pcpathlen) { error = X509Utils.V_ERR_PROXY_PATH_LENGTH_EXCEEDED; - errorDepth = i; - currentCertificate = x; + error_depth = i; + current_cert = x; ok = verifyCallback.call(this, ZERO); if ( ok == 0 ) return ok; } @@ -938,20 +1495,210 @@ public int checkChainExtensions() throws Exception { return 1; } - /** - * c: X509_check_trust + /* + * Check EE or CA certificate purpose. For trusted certificates explicit local + * auxiliary trust can be used to override EKU-restrictions. + * + * x509_vfy.c: static int check_purpose(X509_STORE_CTX *ctx, X509 *x, int purpose, int depth, int must_be_ca) + */ + private int check_purpose(X509AuxCertificate x, int purpose, int depth, byte must_be_ca) throws Exception { + int tr_ok = X509_TRUST_UNTRUSTED; + + /* + * For trusted certificates we want to see whether any auxiliary trust + * settings trump the purpose constraints. + * + * This is complicated by the fact that the trust ordinals in + * ctx->param->trust are entirely independent of the purpose ordinals in + * ctx->param->purpose! + * + * What connects them is their mutual initialization via calls from + * X509_STORE_CTX_set_default() into X509_VERIFY_PARAM_lookup() which sets + * related values of both param->trust and param->purpose. It is however + * typically possible to infer associated trust values from a purpose value + * via the X509_PURPOSE API. + * + * Therefore, we can only check for trust overrides when the purpose we're + * checking is the same as ctx->param->purpose and ctx->param->trust is + * also set. + */ + if (depth >= num_untrusted && purpose == getParam().purpose) { + // TODO JOSSL auxiliary settings aren't properly implemented ... + //tr_ok = Trust.checkTrust(x, getParam().trust, X509_TRUST_NO_SS_COMPAT); // X509_check_trust + } + + switch (tr_ok) { + case X509_TRUST_TRUSTED: + return 1; + case X509_TRUST_REJECTED: + break; + default: + switch (Purpose.checkPurpose(x, getParam().purpose, must_be_ca > 0 ? 1 : 0)) { // X509_check_purpose(x, purpose, must_be_ca) + case 1: + return 1; + case 0: + break; + default: + if ((getParam().flags & V_FLAG_X509_STRICT) == 0) return 1; + } + break; + } + + return verify_cb_cert(x, depth, V_ERR_INVALID_PURPOSE); + } + + /* + * Check a certificate chains extensions for consistency with the supplied purpose + * + * static int check_chain_extensions(X509_STORE_CTX *ctx) */ - public int checkTrust() throws Exception { + private int check_chain_extensions() throws Exception { + byte must_be_ca; int plen = 0; + X509AuxCertificate x; + int proxy_path_length = 0; + int purpose; + boolean allow_proxy_certs; + int num = chain.size(); + + /*- + * must_be_ca can have 1 of 3 values: + * -1: we accept both CA and non-CA certificates, to allow direct + * use of self-signed certificates (which are marked as CA). + * 0: we only accept non-CA certificates. This is currently not + * used, but the possibility is present for future extensions. + * 1: we only accept CA certificates. This is currently used for + * all certificates in the chain except the leaf certificate. + */ + must_be_ca = -1; + + /* CRL path validation */ + if (parent != null) { // NOT IMPLEMENTED: always null + allow_proxy_certs = false; + purpose = X509_PURPOSE_CRL_SIGN; + } else { + allow_proxy_certs = (getParam().flags & V_FLAG_ALLOW_PROXY_CERTS) != 0; + purpose = getParam().purpose; + } + + for (int i = 0; i < num; i++) { + int ret; + x = chain.get(i); + if ((getParam().flags & V_FLAG_IGNORE_CRITICAL) == 0 && unhandledCritical(x)) { + if (verify_cb_cert(x, i, V_ERR_UNHANDLED_CRITICAL_EXTENSION) == 0) + return 0; + } + if (allow_proxy_certs == false && (x.getExFlags() & EXFLAG_PROXY) != 0) { + if (verify_cb_cert(x, i, V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED) == 0) + return 0; + } + ret = Purpose.checkCA(x); // X509_check_ca(x) + switch (must_be_ca) { + case -1: + if ((getParam().flags & V_FLAG_X509_STRICT) != 0 && ret != 1 && ret != 0) { + ret = 0; + this.error = V_ERR_INVALID_CA; + } else { + ret = 1; + } + break; + case 0: + if (ret != 0) { + ret = 0; + this.error = V_ERR_INVALID_NON_CA; + } else { + ret = 1; + } + break; + default: + /* X509_V_FLAG_X509_STRICT is implicit for intermediate CAs */ + if ((ret == 0) || ((i + 1 < num || (getParam().flags & V_FLAG_X509_STRICT) != 0) + && (ret != 1))) { + ret = 0; + this.error = V_ERR_INVALID_CA; + } else + ret = 1; + break; + } + + if (ret == 0 && verify_cb_cert(x, i, V_OK) == 0) + return 0; + /* check_purpose() makes the callback as needed */ + if (purpose > 0 && check_purpose(x, purpose, i, must_be_ca) == 0) + return 0; + /* Check pathlen if not self issued */ + final int ex_pathlen = x.getBasicConstraints(); + if ((i > 1) && (x.getExFlags() & EXFLAG_SI) == 0 + && ex_pathlen != Integer.MAX_VALUE + && ex_pathlen != -1 + && (plen > (ex_pathlen + proxy_path_length + 1))) { + if (verify_cb_cert(x, i, V_ERR_PATH_LENGTH_EXCEEDED) == 0) + return 0; + } + + /* Increment path length if not self issued */ + if ((x.getExFlags() & EXFLAG_SI) == 0) plen++; + + /* + * If this certificate is a proxy certificate, the next certificate + * must be another proxy certificate or a EE certificate. If not, + * the next certificate must be a CA certificate. + */ + final byte[] ex_proxyCertInfo = x.getExtensionValue("1.3.6.1.5.5.7.1.14"); // id-pe-proxyCertInfo(14) + if (ex_proxyCertInfo != null) { // x->ex_flags & EXFLAG_PROXY + ASN1Sequence pci = (ASN1Sequence) new ASN1InputStream(ex_proxyCertInfo).readObject(); + if (pci.size() > 0 && pci.getObjectAt(0) instanceof ASN1Integer) { + int ex_pcpathlen = ((ASN1Integer) pci.getObjectAt(0)).getValue().intValue(); + /* + * RFC3820, 4.1.3 (b)(1) stipulates that if pCPathLengthConstraint + * is less than max_path_length, the former should be copied to + * the latter, and 4.1.4 (a) stipulates that max_path_length + * should be verified to be larger than zero and decrement it. + * + * Because we're checking the certs in the reverse order, we start + * with verifying that proxy_path_length isn't larger than pcPLC, + * and copy the latter to the former if it is, and finally, + * increment proxy_path_length. + */ + if (ex_pcpathlen != -1) { + if (proxy_path_length > ex_pcpathlen) { + if (verify_cb_cert(x, i, V_ERR_PROXY_PATH_LENGTH_EXCEEDED) == 0) + return 0; + } + proxy_path_length = ex_pcpathlen; + } + } + proxy_path_length++; + must_be_ca = 0; + } else { + must_be_ca = 1; + } + } + return 1; + } + + /* Return 1 is a certificate is self signed */ + private boolean cert_self_signed(X509AuxCertificate x) throws CertificateException, IOException { + // Purpose.checkPurpose(x, -1, 0); + if ((x.getExFlags() & EXFLAG_SI) != 0) { // TODO EXFLAG_SS + return true; + } + return false; + } + + // NOTE: does not execute by default due: if ( verifyParameter.trust > 0 ) ... + @Deprecated // legacy check_trust + private int checkTrust() throws Exception { int i,ok; X509AuxCertificate x; i = chain.size()-1; x = chain.get(i); ok = Trust.checkTrust(x, verifyParameter.trust, 0); + if ( ok == X509Utils.X509_TRUST_TRUSTED ) { - return 1; + return 1; // X509_TRUST_TRUSTED } - errorDepth = 1; - currentCertificate = x; + error_depth = 1; + current_cert = x; if ( ok == X509Utils.X509_TRUST_REJECTED ) { error = X509Utils.V_ERR_CERT_REJECTED; } else { @@ -960,32 +1707,117 @@ public int checkTrust() throws Exception { return verifyCallback.call(this, ZERO); } - /** - * c: check_cert_time + /* + * x509_vfy.c: check_trust(X509_STORE_CTX *ctx, int num_untrusted) + */ + private int check_trust(final int num_untrusted) throws Exception { + int i; + final int num = chain.size(); + int trust; + + /* + * Check trusted certificates in chain at depth num_untrusted and up. + * Note, that depths 0..num_untrusted-1 may also contain trusted + * certificates, but the caller is expected to have already checked those, + * and wants to incrementally check just any added since. + */ + for (i = num_untrusted; i < num; i++) { + X509AuxCertificate x = chain.get(i); + trust = Trust.checkTrust(x, getParam().trust, 0); + /* If explicitly trusted return trusted */ + if (trust == X509_TRUST_TRUSTED) + return X509_TRUST_TRUSTED; // goto trusted; + if (trust == X509_TRUST_REJECTED) + return check_trust_rejected(x, i); // goto rejected; + } + + /* + * If we are looking at a trusted certificate, and accept partial chains, + * the chain is PKIX trusted. + */ + if (num_untrusted < num) { + if ((getParam().flags & V_FLAG_PARTIAL_CHAIN) != 0) + return X509_TRUST_TRUSTED; // goto trusted; + return X509_TRUST_UNTRUSTED; + } + + if (num_untrusted == num && (getParam().flags & V_FLAG_PARTIAL_CHAIN) != 0) { + /* + * Last-resort call with no new trusted certificates, check the leaf + * for a direct trust store match. + */ + i = 0; + X509AuxCertificate x = chain.get(i); + X509AuxCertificate mx = lookup_cert_match(x); + if (mx == null) return X509_TRUST_UNTRUSTED; + + /* + * Check explicit auxiliary trust/reject settings. If none are set, + * we'll accept X509_TRUST_UNTRUSTED when not self-signed. + */ + trust = Trust.checkTrust(mx, getParam().trust, 0); + if (trust == X509_TRUST_REJECTED) { + return check_trust_rejected(x, i); // goto rejected; + } + + /* Replace leaf with trusted match */ + chain.set(0, mx); + this.num_untrusted = 0; + return X509_TRUST_TRUSTED; // goto trusted; + } + + /* + * If no trusted certs in chain at all return untrusted and allow + * standard (no issuer cert) etc errors to be indicated. + */ + return X509_TRUST_UNTRUSTED; + } + + private int check_trust_rejected(X509AuxCertificate x, int i) throws Exception { + if (verify_cb_cert(x, i, V_ERR_CERT_REJECTED) == 0) return X509_TRUST_REJECTED; + return X509_TRUST_UNTRUSTED; + } + + /*- + * Check certificate validity times. + * If depth >= 0, invoke verification callbacks on error, otherwise just return + * the validation status. + * + * Return 1 on success, 0 otherwise. */ - public int checkCertificateTime(X509AuxCertificate x) throws Exception { + boolean x509_check_cert_time(X509AuxCertificate x, final int depth) throws Exception { final Date pTime; - if ( (verifyParameter.flags & X509Utils.V_FLAG_USE_CHECK_TIME) != 0 ) { - pTime = this.verifyParameter.checkTime; + if ((getParam().flags & V_FLAG_USE_CHECK_TIME) != 0) { + pTime = getParam().checkTime; + } else if ((getParam().flags & V_FLAG_NO_CHECK_TIME) != 0) { + return true; } else { pTime = Calendar.getInstance().getTime(); } - if ( ! x.getNotBefore().before(pTime) ) { - error = X509Utils.V_ERR_CERT_NOT_YET_VALID; - currentCertificate = x; - if ( verifyCallback.call(this, ZERO) == 0 ) { - return 0; - } + int i = x.getNotBefore().compareTo(pTime); + if (i >= 0 && depth < 0) { + return false; } - if ( ! x.getNotAfter().after(pTime) ) { - error = X509Utils.V_ERR_CERT_HAS_EXPIRED; - currentCertificate = x; - if ( verifyCallback.call(this, ZERO) == 0 ) { - return 0; - } + if (i == 0 && verify_cb_cert(x, depth, V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD) == 0) { + return false; } - return 1; + if (i > 0 && verify_cb_cert(x, depth, V_ERR_CERT_NOT_YET_VALID) == 0) { + return false; + } + + i = x.getNotAfter().compareTo(pTime); + if (i <= 0 && depth < 0) { + return false; + } + if (i == 0 && verify_cb_cert(x, depth, V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD) == 0) { + return false; + } + if (i < 0 && verify_cb_cert(x, depth, V_ERR_CERT_HAS_EXPIRED) == 0) { + return false; + } + + return true; } /** @@ -995,35 +1827,85 @@ public int checkCertificate() throws Exception { final X509CRL[] crl = new X509CRL[1]; X509AuxCertificate x; int ok, cnum; - cnum = errorDepth; + cnum = error_depth; x = chain.get(cnum); - currentCertificate = x; + current_cert = x; + current_issuer = null; + + if (x.getExtensionValue("1.3.6.1.5.5.7.1.14") != null) return 1; // (x.getExFlags() & EXFLAG_PROXY) != 0 + ok = getCRL.call(this, crl, x); if ( ok == 0 ) { error = X509Utils.V_ERR_UNABLE_TO_GET_CRL; ok = verifyCallback.call(this, ZERO); - currentCRL = null; + current_crl = null; return ok; } - currentCRL = crl[0]; + current_crl = crl[0]; ok = checkCRL.call(this, crl[0]); if ( ok == 0 ) { - currentCRL = null; + current_crl = null; return ok; } ok = certificateCRL.call(this, crl[0], x); - currentCRL = null; + current_crl = null; return ok; } - /** - * c: check_crl_time - */ - public int checkCRLTime(X509CRL crl, int notify) throws Exception { - currentCRL = crl; + /* Check CRL times against values in X509_STORE_CTX */ + private boolean check_crl_time(X509CRL crl, final boolean notify) throws Exception { + final Date pTime; + + if (notify) this.current_crl = crl; + + if ((getParam().flags & V_FLAG_USE_CHECK_TIME) != 0) { + pTime = getParam().checkTime; + } else if ((getParam().flags & V_FLAG_NO_CHECK_TIME) != 0) { + return true; + } else { + pTime = Calendar.getInstance().getTime(); + } + + int i = crl.getThisUpdate().compareTo(pTime); + if (i == 0) { + if (!notify) return false; + if (verify_cb_crl(V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD) == 0) + return false; + } + + if (i > 0) { + if (!notify) return false; + if (verify_cb_crl(V_ERR_CRL_NOT_YET_VALID) == 0) + return false; + } + + if (crl.getNextUpdate() != null) { + i = crl.getNextUpdate().compareTo(pTime); + + if (i == 0) { + if (!notify) return false; + if (verify_cb_crl(V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD) == 0) + return false; + } + /* Ignore expiry of base CRL is delta is valid */ + if ((i < 0) /*&& !(ctx->current_crl_score & CRL_SCORE_TIME_DELTA)*/) { + if (!notify) return false; + if (verify_cb_crl(V_ERR_CRL_HAS_EXPIRED) == 0) + return false; + } + } + + if (notify) this.current_crl = null; + + return true; + } + + @Deprecated // legacy check_crl_time + private int checkCRLTime(X509CRL crl, int notify) throws Exception { + current_crl = crl; final Date pTime; - if ( (verifyParameter.flags & X509Utils.V_FLAG_USE_CHECK_TIME) != 0 ) { - pTime = this.verifyParameter.checkTime; + if ((getParam().flags & V_FLAG_USE_CHECK_TIME) != 0) { + pTime = getParam().checkTime; } else { pTime = Calendar.getInstance().getTime(); } @@ -1041,7 +1923,7 @@ public int checkCRLTime(X509CRL crl, int notify) throws Exception { } } - currentCRL = null; + current_crl = null; return 1; } @@ -1068,6 +1950,47 @@ public int getCRLStack(X509CRL[] pcrl, Name name, List crls) throws Exc return 0; } + /* Given a certificate try and find an exact match in the store */ + + private X509AuxCertificate lookup_cert_match(X509AuxCertificate x) throws Exception { + /* Lookup all certs with matching subject name */ + List certs = lookup_certs.call(this, new Name(x.getSubjectX500Principal())); + if (certs == null) return null; + /* Look for exact match */ + for (X509AuxCertificate xtmp : certs) { + if (xtmp.equals(x)) return xtmp; + } + return null; // xtmp = null + } + + /* + * Inform the verify callback of an error. + * If B is not NULL it is the error cert, otherwise use the chain cert at + * B. + * If B is not X509_V_OK, that's the error value, otherwise leave + * unchanged (presumably set by the caller). + * + * Returns 0 to abort verification with an error, non-zero to continue. + */ + private int verify_cb_cert(X509AuxCertificate x, int depth, int err) throws Exception { + this.error_depth = depth; + this.current_cert = x != null ? x : chain.get(depth); + if (err != V_OK) this.error = err; + return verifyCallback.call(this, 0); // ctx->verify_cb(0, ctx) + } + + /*- + * Inform the verify callback of an error, CRL-specific variant. Here, the + * error depth and certificate are already set, we just specify the error + * number. + * + * Returns 0 to abort verification with an error, non-zero to continue. + */ + private int verify_cb_crl(int err) throws Exception { + this.error = err; + return verifyCallback.call(this, 0); // ctx->verify_cb(0, ctx) + } + final static Store.GetIssuerFunction getFirstIssuer = new Store.GetIssuerFunction() { public int call(StoreContext context, X509AuxCertificate[] issuer, X509AuxCertificate cert) throws Exception { return context.getFirstIssuer(issuer, cert); @@ -1079,7 +2002,7 @@ public int call(StoreContext context, X509AuxCertificate[] issuer, X509AuxCertif */ final static Store.GetIssuerFunction getIssuerStack = new Store.GetIssuerFunction() { public int call(StoreContext context, X509AuxCertificate[] issuer, X509AuxCertificate x) throws Exception { - issuer[0] = context.findIssuer(context.otherContext, x); + issuer[0] = context.findIssuer(context.otherContext, x, false); if ( issuer[0] != null ) { return 1; } else { @@ -1088,10 +2011,34 @@ public int call(StoreContext context, X509AuxCertificate[] issuer, X509AuxCertif } }; - /** - * c: check_issued + /* + * Given a possible certificate and issuer check them + * + * x509_vfy.c: static int check_issued(X509_STORE_CTX *ctx, X509 *x, X509 *issuer) */ - final static Store.CheckIssuedFunction defaultCheckIssued = new Store.CheckIssuedFunction() { + final static Store.CheckIssuedFunction check_issued = new Store.CheckIssuedFunction() { + public int call(StoreContext ctx, X509AuxCertificate x, X509AuxCertificate issuer) throws Exception { + int ret; + if (x.equals(issuer)) return ctx.cert_self_signed(x) ? 1 : 0; + ret = checkIfIssuedBy(issuer, x); + if (ret == V_OK) { + /* Special case: single self signed certificate */ + if (ctx.cert_self_signed(x) && ctx.chain.size() == 1) return 1; + + //for (int i = 0; i < chain.size(); i++) { + // X509AuxCertificate ch = chain.get(i); + // if (ch == issuer || ch.equals(issuer)) { + // ret = V_ERR_PATH_LOOP; + // break; + // } + //} + } + + return (ret == V_OK) ? 1 : 0; + } + }; + + final static Store.CheckIssuedFunction check_issued_legacy = new Store.CheckIssuedFunction() { public int call(StoreContext context, X509AuxCertificate cert, X509AuxCertificate issuer) throws Exception { int ret = X509Utils.checkIfIssuedBy(issuer, cert); if ( ret == X509Utils.V_OK ) return 1; @@ -1100,8 +2047,8 @@ public int call(StoreContext context, X509AuxCertificate cert, X509AuxCertificat return 0; } context.error = ret; - context.currentCertificate = cert; - context.currentIssuer = issuer; + context.current_cert = cert; + context.current_issuer = issuer; return context.verifyCallback.call(context, ZERO); } @@ -1116,69 +2063,151 @@ public int call(StoreContext context, Integer outcome) { } }; - /** - * c: internal_verify + /* + * c: static int internal_verify(X509_STORE_CTX *ctx) */ + private int internal_verify() throws Exception { + int n = chain.size() - 1; + X509AuxCertificate xi = chain.get(n); + X509AuxCertificate xs; + + if (checkIssued.call(this, xi, xi) != 0) { + xs = xi; + } else { + if ((getParam().flags & V_FLAG_PARTIAL_CHAIN) != 0) { + xs = xi; + // goto check_cert; + if (!internal_verify_check_cert(this, xi, xs, n)) { + return 0; + } + if (--n >= 0) { + xi = xs; + // xs = ctx.chain.get(n); + } + // goto end + } + if (n <= 0) { + return verify_cb_cert(xi, 0, V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE); + } + + n--; + error_depth = n; + xs = chain.get(n); + } + + /* + * Do not clear ctx->error=0, it must be "sticky", only the user's callback + * is allowed to reset errors (at its own peril). + */ + while ( n >= 0 ) { + /* + * Skip signature check for self signed certificates unless explicitly + * asked for. It doesn't add any security and just wastes time. If + * the issuer's public key is unusable, report the issuer certificate + * and its depth (rather than the depth of the subject). + */ + if (xs != xi || (getParam().flags & V_FLAG_CHECK_SS_SIGNATURE) != 0) { + PublicKey pkey = xi.getPublicKey(); + if (pkey == null) { + if (verify_cb_cert(xi, xi != xs ? n+1 : n, V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) == 0) + return 0; + } else if (!X509_verify(xs, pkey)) { + if (verify_cb_cert(xs, n, V_ERR_CERT_SIGNATURE_FAILURE) == 0) + return 0; + } + } + + // check_cert : + if (!internal_verify_check_cert(this, xi, xs, n)) { + return 0; + } + if (--n >= 0) { + xi = xs; + xs = chain.get(n); + } + // end + } + return 1; + } + + // goto check_cert: + private static boolean internal_verify_check_cert( + final StoreContext ctx, X509AuxCertificate xi, X509AuxCertificate xs, int n) + throws Exception { + /* Calls verify callback as needed */ + if (!ctx.x509_check_cert_time(xs, n)) + return false; + + /* + * Signal success at this depth. However, the previous error (if any) + * is retained. + */ + ctx.current_issuer = xi; + ctx.current_cert = xs; + ctx.error_depth = n; + if (ctx.verifyCallback.call(ctx, 1) == 0) + return false; + + return true; // do not halt yet but : + //if (--n >= 0) { + // xi = xs; + // xs = ctx.chain.get(n); + //} + } + + private static boolean X509_verify(X509AuxCertificate xs, PublicKey pkey) { + if (xs.verified) return true; + + try { + xs.verify(pkey); + } catch (Exception e) { + return false; + } + return xs.verified = true; + } + + @Deprecated // legacy internal_verify final static Store.VerifyFunction internalVerify = new Store.VerifyFunction() { public int call(final StoreContext context) throws Exception { Store.VerifyCallbackFunction verifyCallback = context.verifyCallback; int n = context.chain.size(); - context.errorDepth = n - 1; + context.error_depth = n - 1; n--; X509AuxCertificate xi = context.chain.get(n); X509AuxCertificate xs = null; int ok; - if ( context.checkIssued.call(context,xi,xi) != 0 ) { + if (context.checkIssued.call(context, xi, xi) != 0) { xs = xi; - } - else { - if ( n <= 0 ) { + } else { + if (n <= 0) { context.error = X509Utils.V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE; - context.currentCertificate = xi; + context.current_cert = xi; ok = verifyCallback.call(context, ZERO); return ok; - } - else { + } else { n--; - context.errorDepth = n; + context.error_depth = n; xs = context.chain.get(n); } } - while ( n >= 0 ) { - context.errorDepth = n; - if ( ! xs.isValid() ) { - try { - xs.verify(xi.getPublicKey()); - } - catch(Exception e) { - /* - System.err.println("n: " + n); - System.err.println("verifying: " + xs); - System.err.println("verifying with issuer?: " + xi); - System.err.println("verifying with issuer.key?: " + xi.getPublicKey()); - System.err.println("exception: " + e); - */ - context.error = X509Utils.V_ERR_CERT_SIGNATURE_FAILURE; - context.currentCertificate = xs; - ok = verifyCallback.call(context, ZERO); - if ( ok == 0 ) return ok; - } + while (n >= 0) { + context.error_depth = n; + if (!X509_verify(xs, xi.getPublicKey())) { + context.error = X509Utils.V_ERR_CERT_SIGNATURE_FAILURE; + context.current_cert = xs; + ok = verifyCallback.call(context, ZERO); + if (ok == 0) return ok; } - xs.setValid(true); - ok = context.checkCertificateTime(xs); - if ( ok == 0 ) return ok; - - context.currentIssuer = xi; - context.currentCertificate = xs; - ok = verifyCallback.call(context, Integer.valueOf(1)); - if ( ok == 0 ) return ok; + if (!internal_verify_check_cert(context, xi, xs, n)) { + return 0; + } n--; - if ( n >= 0 ) { + if (n >= 0) { xi = xs; xs = context.chain.get(n); } @@ -1188,26 +2217,26 @@ public int call(final StoreContext context) throws Exception { } }; - /** - * c: check_revocation + /* + * c: static int check_revocation(X509_STORE_CTX *ctx) */ - final static Store.CheckRevocationFunction defaultCheckRevocation = new Store.CheckRevocationFunction() { - public int call(final StoreContext context) throws Exception { - if ( (context.verifyParameter.flags & X509Utils.V_FLAG_CRL_CHECK) == 0 ) { + final static Store.CheckRevocationFunction check_revocation = new Store.CheckRevocationFunction() { + public int call(final StoreContext ctx) throws Exception { + if ( (ctx.getParam().flags & V_FLAG_CRL_CHECK) == 0 ) { return 1; } final int last; - if ( (context.verifyParameter.flags & X509Utils.V_FLAG_CRL_CHECK_ALL) != 0 ) { - last = context.chain.size() - 1; - } - else { + if ( (ctx.getParam().flags & V_FLAG_CRL_CHECK_ALL) != 0 ) { + last = ctx.chain.size() - 1; + } else { + /* If checking CRL paths this isn't the EE certificate */ + if (ctx.parent != null) return 1; // NOT IMPLEMENTED: always null last = 0; } - int ok; for ( int i=0; i<=last; i++ ) { - context.errorDepth = i; - ok = context.checkCertificate(); - if ( ok == 0 ) return 0; + ctx.error_depth = i; + int ok = ctx.checkCertificate(); // check_cert(ctx); + if (ok == 0) return 0; } return 1; } @@ -1239,12 +2268,96 @@ public int call(final StoreContext context, final X509CRL[] crls, X509AuxCertifi } }; - /** - * c: check_crl - */ - final static Store.CheckCRLFunction defaultCheckCRL = new Store.CheckCRLFunction() { + // TODO unused due incomplete - needs score support to pass test_x509store.rb tests? + /* Check CRL validity */ + private int check_crl(X509CRL crl) throws Exception { + final X509AuxCertificate issuer; + int cnum = this.error_depth; + int chnum = this.chain.size() - 1; + + /* if we have an alternative CRL issuer cert use that */ + if (this.current_issuer != null) + issuer = this.current_issuer; + /* + * Else find CRL issuer: if not last certificate then issuer is next + * certificate in chain. + */ + else if (cnum < chnum) + issuer = this.chain.get(cnum + 1); + else { + issuer = this.chain.get(chnum); + /* If not self signed, can't check signature */ + if (this.checkIssued.call(this, issuer, issuer) == 0 && + verify_cb_crl(V_ERR_UNABLE_TO_GET_CRL_ISSUER) == 0) + return 0; + } + + if (issuer == null) { + return 1; + } + + /* + * Skip most tests for deltas because they have already been done + */ + //if (!crl->base_crl_number) { + /* Check for cRLSign bit if keyUsage present */ + if (issuer.getKeyUsage() != null && !issuer.getKeyUsage()[6]) { + if (verify_cb_crl(V_ERR_KEYUSAGE_NO_CRL_SIGN) == 0) return 0; + } + + //if (!(ctx->current_crl_score & CRL_SCORE_SCOPE) && + // !verify_cb_crl(ctx, X509_V_ERR_DIFFERENT_CRL_SCOPE)) + // return 0; + // + //if (!(ctx->current_crl_score & CRL_SCORE_SAME_PATH) && + // check_crl_path(ctx, ctx->current_issuer) <= 0 && + // !verify_cb_crl(ctx, X509_V_ERR_CRL_PATH_VALIDATION_ERROR)) + // return 0; + // + //if ((crl->idp_flags & IDP_INVALID) && + // !verify_cb_crl(ctx, X509_V_ERR_INVALID_EXTENSION)) + // return 0; + //} + + //if (!(ctx->current_crl_score & CRL_SCORE_TIME) && + // !check_crl_time(ctx, crl, 1)) + // return 0; + if (!check_crl_time(crl, true)) return 0; + + /* Attempt to get issuer certificate public key */ + final PublicKey ikey = issuer.getPublicKey(); // X509_get0_pubkey(issuer) + + if (ikey == null && verify_cb_crl(V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY) == 0) { + return 0; + } + + if (ikey != null) { + //int rv = X509_CRL_check_suiteb(crl, ikey, ctx->param->flags); + // + //if (rv != V_OK && verify_cb_crl(rv) == 0) { + // return 0; + //} + /* Verify CRL signature */ + try { + SecurityHelper.verify(crl, ikey); // X509_CRL_verify + } + catch (GeneralSecurityException ex) { + if (verify_cb_crl(V_ERR_CRL_SIGNATURE_FAILURE) == 0) return 0; + } + } + return 1; + } + + /* Check CRL validity */ + final static Store.CheckCRLFunction check_crl = new Store.CheckCRLFunction() { + public int call(final StoreContext ctx, final X509CRL crl) throws Exception { + return ctx.check_crl(crl); + } + }; + + final static Store.CheckCRLFunction check_crl_legacy = new Store.CheckCRLFunction() { public int call(final StoreContext context, final X509CRL crl) throws Exception { - final int errorDepth = context.errorDepth; + final int errorDepth = context.error_depth; final int lastInChain = context.chain.size() - 1; int ok; @@ -1285,8 +2398,9 @@ public int call(final StoreContext context, final X509CRL crl) throws Exception } } - ok = context.checkCRLTime(crl, 1); - if ( ok == 0 ) return ok; + //ok = context.checkCRLTime(crl, 1); + //if ( ok == 0 ) return ok; + if (!context.check_crl_time(crl, true)) return 0; return 1; } @@ -1315,12 +2429,14 @@ public int call(final StoreContext context, final X509CRL crl, X509AuxCertificat } }; - /** - * c: check_policy + /* + * c: static int check_policy(X509_STORE_CTX *ctx) */ - final static CheckPolicyFunction defaultCheckPolicy = new CheckPolicyFunction() { + final static CheckPolicyFunction check_policy = new CheckPolicyFunction() { public int call(StoreContext context) throws Exception { + // NOTE: NOT IMPLEMENTED return 1; } }; + }// X509_STORE_CTX diff --git a/src/main/java/org/jruby/ext/openssl/x509store/Trust.java b/src/main/java/org/jruby/ext/openssl/x509store/Trust.java index 4c19017c..054b375c 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/Trust.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/Trust.java @@ -27,10 +27,15 @@ ***** END LICENSE BLOCK *****/ package org.jruby.ext.openssl.x509store; +import org.jruby.ext.openssl.impl.ASN1Registry; + +import java.io.IOException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; +import static org.jruby.ext.openssl.x509store.X509Utils.*; + /** * c: X509_TRUST * @@ -38,7 +43,9 @@ */ public class Trust { - static interface Checker extends Function3 {} + interface Checker { + int call(T arg0, X509AuxCertificate x, int flags) throws Exception; + } public int trust; public int flags; @@ -70,18 +77,24 @@ public static Checker setDefault(Checker trust) { private final static List trustable = new ArrayList(); + private static final String OID_anyExtendedKeyUsage = ASN1Registry.nid2oid(ASN1Registry.NID_anyExtendedKeyUsage); + /** * c: X509_check_trust */ public static int checkTrust(X509AuxCertificate x, int id, int flags) throws Exception { - if ( id == -1 ) return 1; + /* We get this as a default value */ + if (id == X509_TRUST_DEFAULT) { + // return obj_trust(NID_anyExtendedKeyUsage, x, flags | X509_TRUST_DO_SS_COMPAT); + return objTrust.call(OID_anyExtendedKeyUsage, x, flags | X509_TRUST_DO_SS_COMPAT); + } int idx = getByID(id); if (idx == -1) { - return defaultTrust.call(Integer.toString(id), x, Integer.valueOf(flags)); + return defaultTrust.call(Integer.toString(id), x, flags); } - Trust pt = getFirst(idx); - return pt.checkTrust.call(pt, x, Integer.valueOf(flags)); + Trust pt = get0(idx); + return pt.checkTrust.call(pt, x, flags); } /** @@ -94,11 +107,11 @@ public static int getCount() { /** * c: X509_TRUST_get0 */ - public static Trust getFirst(int idx) { - if(idx < 0) { + private static Trust get0(int idx) { + if (idx < 0) { return null; } - if(idx < trstandard.length) { + if (idx < trstandard.length) { return trstandard[idx]; } return trustable.get(idx - trstandard.length); @@ -145,7 +158,7 @@ static int add(int id, int flags, Checker ck, String name, String arg1, O trtmp = new Trust(); trtmp.flags = X509Utils.X509_TRUST_DYNAMIC; } else { - trtmp = getFirst(idx); + trtmp = get0(idx); } trtmp.name = name; trtmp.flags &= X509Utils.X509_TRUST_DYNAMIC; @@ -191,15 +204,27 @@ public int getTrust() { /** * c: trust_compat */ + private static int trust_compat(X509AuxCertificate x, int flags) + throws CertificateException, IOException { + /* Call for side-effect of computing hash and caching extensions */ + if (Purpose.checkPurpose(x, -1, 0) != 1) // X509_check_purpose + return X509_TRUST_UNTRUSTED; + // TODO flip to using EXFLAG_SS once legacy (verify) compatibility is dropped + //if ((flags & X509_TRUST_NO_SS_COMPAT) == 0 && (x.getExFlags() & EXFLAG_SS) != 0) + if ((flags & X509_TRUST_NO_SS_COMPAT) == 0 && + x.getIssuerX500Principal().equals( x.getSubjectX500Principal() )) // self signed + return X509_TRUST_TRUSTED; + return X509_TRUST_UNTRUSTED; + } + final static Checker trustCompatibe = new Checker() { - public int call(final Trust trust, - final X509AuxCertificate x, final Integer flags) throws CertificateException { - Purpose.checkPurpose(x,-1,0); - if ( x.getIssuerX500Principal().equals( x.getSubjectX500Principal() ) ) { // self signed - return X509Utils.X509_TRUST_TRUSTED; - } else { - return X509Utils.X509_TRUST_UNTRUSTED; - } + public int call(Trust trust, X509AuxCertificate x, int flags) throws Exception { + return trust_compat(x, flags); + } + + @Override + public String toString() { + return "trustCompatibe"; // only for debugging } }; @@ -207,50 +232,89 @@ public int call(final Trust trust, * c: trust_1oidany */ final static Checker trust1OIDAny = new Checker() { - public int call(final Trust trust, - final X509AuxCertificate x, final Integer flags) throws Exception { + public int call(Trust trust, X509AuxCertificate x, int flags) throws Exception { final X509Aux aux = x.aux; if ( aux != null && ( aux.trust.size() > 0 || aux.reject.size() > 0 ) ) { return objTrust.call(trust.arg1, x, flags); } return trustCompatibe.call(trust, x, flags); } + + @Override + public String toString() { + return "trust1OIDAny"; // only for debugging + } }; /** * c: trust_1oid */ final static Checker trust1OID = new Checker() { - public int call(final Trust trust, - final X509AuxCertificate x, final Integer flags) throws Exception { + public int call(Trust trust, X509AuxCertificate x, int flags) throws Exception { if ( x.aux != null ) { return objTrust.call(trust.arg1, x, flags); } return X509Utils.X509_TRUST_UNTRUSTED; } + + @Override + public String toString() { + return "trust1OID"; // only for debugging + } }; /** * c: obj_trust */ final static Checker objTrust = new Checker() { - public int call(final String id, - final X509AuxCertificate x, final Integer flags) { + public int call(final String id, X509AuxCertificate x, int flags) throws Exception { final X509Aux aux = x.aux; - if ( aux == null ) { - return X509Utils.X509_TRUST_UNTRUSTED; - } - for ( String rejectId : aux.reject ) { - if ( rejectId.equals(id) ) { - return X509Utils.X509_TRUST_REJECTED; + + if ( aux != null && aux.reject != null ) { + for ( String oid : aux.reject ) { + if (oid.equals(id) || (oid.equals(OID_anyExtendedKeyUsage) && + (flags & X509_TRUST_OK_ANY_EKU) != 0)) { + return X509_TRUST_REJECTED; + } } } - for ( String trustId : aux.trust ) { - if ( trustId.equals(id) ) { - return X509Utils.X509_TRUST_TRUSTED; + + if ( aux != null && aux.trust != null ) { + for ( String oid : aux.trust ) { + if (oid.equals(id) || (oid.equals(OID_anyExtendedKeyUsage) && + (flags & X509_TRUST_OK_ANY_EKU) != 0)) { + return X509_TRUST_TRUSTED; + } } + /* + * Reject when explicit trust EKU are set and none match. + * + * Returning untrusted is enough for for full chains that end in + * self-signed roots, because when explicit trust is specified it + * suppresses the default blanket trust of self-signed objects. + * + * But for partial chains, this is not enough, because absent a similar + * trust-self-signed policy, non matching EKUs are indistinguishable + * from lack of EKU constraints. + * + * Therefore, failure to match any trusted purpose must trigger an + * explicit reject. + */ + return X509_TRUST_REJECTED; } - return X509Utils.X509_TRUST_UNTRUSTED; + + if ((flags & X509_TRUST_DO_SS_COMPAT) == 0) + return X509_TRUST_UNTRUSTED; + + /* + * Not rejected, and there is no list of accepted uses, try compat. + */ + return trust_compat(x, flags); + } + + @Override + public String toString() { + return "objTrust"; // only for debugging } }; diff --git a/src/main/java/org/jruby/ext/openssl/x509store/VerifyParameter.java b/src/main/java/org/jruby/ext/openssl/x509store/VerifyParameter.java index 0234f227..2e28f1e0 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/VerifyParameter.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/VerifyParameter.java @@ -35,6 +35,8 @@ import org.bouncycastle.asn1.ASN1Primitive; +import static org.jruby.ext.openssl.x509store.X509Utils.*; + /** * c: X509_VERIFY_PARAM * @@ -88,12 +90,12 @@ public void free() { /** * c: X509_VERIFY_PARAM_inherit */ - public int inherit(VerifyParameter src) { + public void inherit(VerifyParameter src) { long inh_flags; boolean to_d, to_o; - if(src == null) { - return 1; + if (src == null) { + return; } @@ -102,7 +104,7 @@ public int inherit(VerifyParameter src) { this.inheritFlags = 0; } if((inh_flags & X509Utils.X509_VP_FLAG_LOCKED) != 0) { - return 1; + return; } to_d = ((inh_flags & X509Utils.X509_VP_FLAG_DEFAULT) != 0); to_o = ((inh_flags & X509Utils.X509_VP_FLAG_OVERWRITE) != 0); @@ -128,18 +130,18 @@ public int inherit(VerifyParameter src) { this.flags |= src.flags; - if(to_o || ((src.policies != null && (to_d || this.policies == null)))) { + if (to_o || ((src.policies != null && (to_d || this.policies == null)))) { setPolicies(src.policies); } - return 1; + return; } /** * c: X509_VERIFY_PARAM_set1 */ - public int set(VerifyParameter from) { + void set(VerifyParameter from) { inheritFlags |= X509Utils.X509_VP_FLAG_DEFAULT; - return inherit(from); + inherit(from); } /** @@ -257,6 +259,20 @@ public int addTable() { return 1; } + @Override + public String toString() { + return "VerifyParameter{" + + "name='" + name + '\'' + + ", checkTime=" + checkTime + + ", inheritFlags=" + inheritFlags + + ", flags=" + flags + + ", purpose=" + purpose + + ", trust=" + trust + + ", depth=" + depth + + ", policies=" + policies + + '}'; + } + public static VerifyParameter lookup(String name) { for(VerifyParameter v : parameterTable) { if(name.equals(v.name)) { @@ -280,44 +296,44 @@ public static void tableCleanup() { private final static VerifyParameter[] defaultTable = new VerifyParameter[] { new VerifyParameter( - "default", /* X509 default parameters */ - 0, /* Check time */ - 0, /* internal flags */ - 0, /* flags */ - 0, /* purpose */ - 0, /* trust */ - 100, /* depth */ - null /* policies */ + "default", /* X509 default parameters */ + 0, /* Check time */ + 0, /* internal flags */ + V_FLAG_TRUSTED_FIRST, /* flags */ + 0, /* purpose */ + 0, /* trust */ + 100, /* depth */ + null /* policies */ ), new VerifyParameter( - "pkcs7", /* SSL/TLS client parameters */ - 0, /* Check time */ - 0, /* internal flags */ - 0, /* flags */ - X509Utils.X509_PURPOSE_SMIME_SIGN, /* purpose */ - X509Utils.X509_TRUST_EMAIL, /* trust */ - -1, /* depth */ - null /* policies */ + "pkcs7", /* S/MIME sign parameters */ + 0, /* Check time */ + 0, /* internal flags */ + 0, /* flags */ + X509_PURPOSE_SMIME_SIGN, /* purpose */ + X509_TRUST_EMAIL, /* trust */ + -1, /* depth */ + null /* policies */ ), new VerifyParameter( - "ssl_client", /* SSL/TLS client parameters */ - 0, /* Check time */ - 0, /* internal flags */ - 0, /* flags */ - X509Utils.X509_PURPOSE_SSL_CLIENT, /* purpose */ - X509Utils.X509_TRUST_SSL_CLIENT, /* trust */ - -1, /* depth */ - null /* policies */ + "ssl_client", /* SSL/TLS client parameters */ + 0, /* Check time */ + 0, /* internal flags */ + 0, /* flags */ + X509_PURPOSE_SSL_CLIENT, /* purpose */ + X509_TRUST_SSL_CLIENT, /* trust */ + -1, /* depth */ + null /* policies */ ), new VerifyParameter( - "ssl_server", /* SSL/TLS server parameters */ - 0, /* Check time */ - 0, /* internal flags */ - 0, /* flags */ - X509Utils.X509_PURPOSE_SSL_SERVER, /* purpose */ - X509Utils.X509_TRUST_SSL_SERVER, /* trust */ - -1, /* depth */ - null /* policies */ + "ssl_server", /* SSL/TLS server parameters */ + 0, /* Check time */ + 0, /* internal flags */ + 0, /* flags */ + X509_PURPOSE_SSL_SERVER, /* purpose */ + X509_TRUST_SSL_SERVER, /* trust */ + -1, /* depth */ + null /* policies */ )}; private final static List parameterTable = new ArrayList(); diff --git a/src/main/java/org/jruby/ext/openssl/x509store/X509AuxCertificate.java b/src/main/java/org/jruby/ext/openssl/x509store/X509AuxCertificate.java index 4e6713b8..81788578 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/X509AuxCertificate.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/X509AuxCertificate.java @@ -31,6 +31,7 @@ import java.io.ByteArrayInputStream; import java.math.BigInteger; +import java.util.Arrays; import java.util.Date; import java.util.Collection; import java.util.List; @@ -52,9 +53,15 @@ import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1OctetString; +import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.Certificate; +import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.jce.provider.X509CertificateObject; import org.jruby.ext.openssl.SecurityHelper; @@ -73,8 +80,8 @@ public class X509AuxCertificate extends X509Certificate implements Cloneable { final X509Aux aux; - private boolean valid = false; - private int ex_flags = 0; + boolean verified = false; // opt: avoids doing internal_verify twice + private int ex_flags = -1; public X509AuxCertificate(Certificate wrap) throws IOException, CertificateException { super(); @@ -105,25 +112,80 @@ public final X509AuxCertificate clone() { final X509AuxCertificate cloneForCache() { final X509AuxCertificate clone = clone(); - clone.valid = false; - clone.ex_flags = 0; + clone.verified = false; + clone.ex_flags = -1; return clone; } - public boolean isValid() { - return valid; + public int getExFlags() throws IOException { + if (ex_flags == -1) { + //try { + ex_flags = computeExFlags(); + //} catch (IOException e) { + // throw new IllegalStateException(e); + //} + } + return ex_flags; } - public void setValid(boolean v) { - this.valid = v; - } + // NOTE: not all EXFLAGS are implemented! + private int computeExFlags() throws IOException { + int flags = 0; - public int getExFlags() { - return ex_flags; - } + /* V1 should mean no extensions ... */ + if (getVersion() == 1) { + flags |= X509Utils.EXFLAG_V1; + } + + if (getExtensionValue("2.5.29.19") != null) { // BASIC_CONSTRAINTS + if (getBasicConstraints() != -1) { // is CA + flags |= X509Utils.EXFLAG_CA; + } + flags |= X509Utils.EXFLAG_BCONS; + } + + if (getSubjectX500Principal().equals(getIssuerX500Principal())) { + flags |= X509Utils.EXFLAG_SI; /* Cert is self-issued */ + + // TODO duplicate code from X509Utils.checkIfIssuedBy + if (getExtensionValue("2.5.29.35") != null) { //authorityKeyID + Object key = X509Utils.get(getExtensionValue("2.5.29.35")); + if (!(key instanceof ASN1Sequence)) key = X509Utils.get((DEROctetString) key); + + final ASN1Sequence seq = (ASN1Sequence) key; + final AuthorityKeyIdentifier akid; + if (seq.size() == 1 && (seq.getObjectAt(0) instanceof ASN1OctetString)) { + akid = AuthorityKeyIdentifier.getInstance(new DLSequence(new DERTaggedObject(0, seq.getObjectAt(0)))); + } else { + akid = AuthorityKeyIdentifier.getInstance(seq); + } + + if (akid.getKeyIdentifier() != null) { + if (getExtensionValue("2.5.29.14") != null) { + DEROctetString der = (DEROctetString) X509Utils.get(getExtensionValue("2.5.29.14")); + SubjectKeyIdentifier skid = SubjectKeyIdentifier.getInstance(X509Utils.get(der.getOctets())); + if (skid.getKeyIdentifier() != null) { + if (Arrays.equals(akid.getKeyIdentifier(), skid.getKeyIdentifier())) { /* SKID matches AKID */ + /* .. and the signature alg matches the PUBKEY alg: */ + if (getSigAlgName().equals(getPublicKey().getAlgorithm())) { + flags |= X509Utils.EXFLAG_SS; /* indicate self-signed */ + } + } + } + } + } + } + } + + if (getKeyUsage() != null) { + flags |= X509Utils.EXFLAG_XKUSAGE; + } + + if (getExtensionValue("1.3.6.1.5.5.7.1.14") != null) { + flags |= X509Utils.EXFLAG_PROXY; + } - public void setExFlags(int ex_flags) { - this.ex_flags = ex_flags; + return flags; } // DELEGATES : diff --git a/src/main/java/org/jruby/ext/openssl/x509store/X509Error.java b/src/main/java/org/jruby/ext/openssl/x509store/X509Error.java index be3a3856..62081cdb 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/X509Error.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/X509Error.java @@ -100,12 +100,17 @@ public static String getMessage(int reason) { } } - private static ThreadLocal> errors = new ThreadLocal>(); + private static final ThreadLocal> errors = new ThreadLocal>(); public static void addError(int reason) { getErrors().put(reason, getMessage(reason)); } + // define X509err(f, r) ERR_raise_data(ERR_LIB_X509, (r), NULL) + //public static void addError(int f, int r) { + // getErrors().put(r, getMessage(r)); + //} + public static void clearErrors() { synchronized (errors) { if ( errors.get() != null ) newErrorsMap(); @@ -144,7 +149,8 @@ public static Map getErrorsImpl(boolean required) { private static Map newErrorsMap() { Map map = new LinkedHashMap(8); - errors.set(map); return map; + errors.set(map); + return map; } }// Err diff --git a/src/main/java/org/jruby/ext/openssl/x509store/X509Object.java b/src/main/java/org/jruby/ext/openssl/x509store/X509Object.java index 7afe8ca5..f3f8bec6 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/X509Object.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/X509Object.java @@ -68,15 +68,11 @@ public static X509Object retrieveMatch(final Collection li return null; } - public boolean isName(Name nm) { - return false; - } + public abstract boolean isName(Name nm) ; - public boolean matches(X509Object o) { - return false; - } + public abstract boolean matches(X509Object o) ; - public abstract int type(); + public abstract int type() ; public int compareTo(X509Object other) { return type() - other.type(); diff --git a/src/main/java/org/jruby/ext/openssl/x509store/X509Utils.java b/src/main/java/org/jruby/ext/openssl/x509store/X509Utils.java index 3fd7051a..6ac9d052 100644 --- a/src/main/java/org/jruby/ext/openssl/x509store/X509Utils.java +++ b/src/main/java/org/jruby/ext/openssl/x509store/X509Utils.java @@ -105,110 +105,119 @@ public static String getDefaultCertificateFileEnvironment() { */ public static String verifyCertificateErrorString(final int error) { switch (error) { - case V_OK: return("ok"); - case V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case V_OK: + return("ok"); + case V_ERR_UNABLE_TO_GET_ISSUER_CERT: return("unable to get issuer certificate"); - case V_ERR_UNABLE_TO_GET_CRL: + case V_ERR_UNABLE_TO_GET_CRL: return("unable to get certificate CRL"); - case V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: return("unable to decrypt certificate's signature"); - case V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: return("unable to decrypt CRL's signature"); - case V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + case V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: return("unable to decode issuer public key"); - case V_ERR_CERT_SIGNATURE_FAILURE: + case V_ERR_CERT_SIGNATURE_FAILURE: return("certificate signature failure"); - case V_ERR_CRL_SIGNATURE_FAILURE: + case V_ERR_CRL_SIGNATURE_FAILURE: return("CRL signature failure"); - case V_ERR_CERT_NOT_YET_VALID: + case V_ERR_CERT_NOT_YET_VALID: return("certificate is not yet valid"); - case V_ERR_CRL_NOT_YET_VALID: + case V_ERR_CRL_NOT_YET_VALID: return("CRL is not yet valid"); - case V_ERR_CERT_HAS_EXPIRED: + case V_ERR_CERT_HAS_EXPIRED: return("certificate has expired"); - case V_ERR_CRL_HAS_EXPIRED: + case V_ERR_CRL_HAS_EXPIRED: return("CRL has expired"); - case V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: return("format error in certificate's notBefore field"); - case V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: return("format error in certificate's notAfter field"); - case V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: return("format error in CRL's lastUpdate field"); - case V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + case V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: return("format error in CRL's nextUpdate field"); - case V_ERR_OUT_OF_MEM: + case V_ERR_OUT_OF_MEM: return("out of memory"); - case V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: return("self signed certificate"); - case V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case V_ERR_SELF_SIGNED_CERT_IN_CHAIN: return("self signed certificate in certificate chain"); - case V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: return("unable to get local issuer certificate"); - case V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: return("unable to verify the first certificate"); - case V_ERR_CERT_CHAIN_TOO_LONG: + case V_ERR_CERT_CHAIN_TOO_LONG: return("certificate chain too long"); - case V_ERR_CERT_REVOKED: + case V_ERR_CERT_REVOKED: return("certificate revoked"); - case V_ERR_INVALID_CA: + case V_ERR_INVALID_CA: return ("invalid CA certificate"); - case V_ERR_INVALID_NON_CA: - return ("invalid non-CA certificate (has CA markings)"); - case V_ERR_PATH_LENGTH_EXCEEDED: + case V_ERR_PATH_LENGTH_EXCEEDED: return ("path length constraint exceeded"); - case V_ERR_PROXY_PATH_LENGTH_EXCEEDED: - return("proxy path length constraint exceeded"); - case V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: - return("proxy cerificates not allowed, please set the appropriate flag"); - case V_ERR_INVALID_PURPOSE: + case V_ERR_INVALID_PURPOSE: return ("unsupported certificate purpose"); - case V_ERR_CERT_UNTRUSTED: + case V_ERR_CERT_UNTRUSTED: return ("certificate not trusted"); - case V_ERR_CERT_REJECTED: + case V_ERR_CERT_REJECTED: return ("certificate rejected"); - case V_ERR_APPLICATION_VERIFICATION: - return("application verification failure"); - case V_ERR_SUBJECT_ISSUER_MISMATCH: + case V_ERR_SUBJECT_ISSUER_MISMATCH: return("subject issuer mismatch"); - case V_ERR_AKID_SKID_MISMATCH: + case V_ERR_AKID_SKID_MISMATCH: return("authority and subject key identifier mismatch"); - case V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case V_ERR_AKID_ISSUER_SERIAL_MISMATCH: return("authority and issuer serial number mismatch"); - case V_ERR_KEYUSAGE_NO_CERTSIGN: + case V_ERR_KEYUSAGE_NO_CERTSIGN: return("key usage does not include certificate signing"); - case V_ERR_UNABLE_TO_GET_CRL_ISSUER: + case V_ERR_UNABLE_TO_GET_CRL_ISSUER: return("unable to get CRL issuer certificate"); - case V_ERR_UNHANDLED_CRITICAL_EXTENSION: + case V_ERR_UNHANDLED_CRITICAL_EXTENSION: return("unhandled critical extension"); - case V_ERR_KEYUSAGE_NO_CRL_SIGN: + case V_ERR_KEYUSAGE_NO_CRL_SIGN: return("key usage does not include CRL signing"); - case V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: - return("key usage does not include digital signature"); - case V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: + case V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: return("unhandled critical CRL extension"); - case V_ERR_INVALID_EXTENSION: + case V_ERR_INVALID_NON_CA: + return ("invalid non-CA certificate (has CA markings)"); + case V_ERR_PROXY_PATH_LENGTH_EXCEEDED: + return("proxy path length constraint exceeded"); + case V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: + return("key usage does not include digital signature"); + case V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: + return("proxy cerificates not allowed, please set the appropriate flag"); + case V_ERR_INVALID_EXTENSION: return("invalid or inconsistent certificate extension"); - case V_ERR_INVALID_POLICY_EXTENSION: + case V_ERR_INVALID_POLICY_EXTENSION: return("invalid or inconsistent certificate policy extension"); - case V_ERR_NO_EXPLICIT_POLICY: + case V_ERR_NO_EXPLICIT_POLICY: return("no explicit policy"); + case V_ERR_APPLICATION_VERIFICATION: + return("application verification failure"); + case V_ERR_PATH_LOOP: + return("Path Loop"); + case V_ERR_HOSTNAME_MISMATCH: + return("Hostname mismatch"); + case V_ERR_EMAIL_MISMATCH: + return("Email address mismatch"); + case V_ERR_IP_ADDRESS_MISMATCH: + return("IP address mismatch"); default: return "error number " + error; } } - private static ASN1Primitive get(DEROctetString str) throws IOException { + static ASN1Primitive get(DEROctetString str) throws IOException { return get( str.getOctets() ); } - private static ASN1Primitive get(final byte[] input) throws IOException { + static ASN1Primitive get(final byte[] input) throws IOException { return new ASN1InputStream(input).readObject(); } - /** - * c: X509_check_issued + /* + * c: X509_check_issued + x509_likely_issued + x509_signing_allowed */ - public static int checkIfIssuedBy(final X509AuxCertificate issuer, + static int checkIfIssuedBy(final X509AuxCertificate issuer, final X509AuxCertificate subject) throws IOException { if ( ! issuer.getSubjectX500Principal().equals(subject.getIssuerX500Principal()) ) { @@ -365,6 +374,7 @@ else if (maybeCertFile != null && new File(maybeCertFile).exists()) { public static final int X509_L_ADD_DIR = 2; public static final int V_OK = 0; + public static final int V_ERR_UNSPECIFIED = 1; public static final int V_ERR_UNABLE_TO_GET_ISSUER_CERT = 2; public static final int V_ERR_UNABLE_TO_GET_CRL = 3; public static final int V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = 4; @@ -413,25 +423,63 @@ else if (maybeCertFile != null && new File(maybeCertFile).exists()) { public static final int V_ERR_APPLICATION_VERIFICATION = 50; - public static final int V_FLAG_CB_ISSUER_CHECK = 0x1; + /* Another issuer check debug option */ + public static final int V_ERR_PATH_LOOP = 55; + + /* Host, email and IP check errors */ + public static final int V_ERR_HOSTNAME_MISMATCH = 62; + public static final int V_ERR_EMAIL_MISMATCH = 63; + public static final int V_ERR_IP_ADDRESS_MISMATCH = 64; + + // ... + + /* security level errors */ + //public static final int V_ERR_EE_KEY_TOO_SMALL = 66; + //public static final int V_ERR_CA_KEY_TOO_SMALL = 67; + //public static final int V_ERR_CA_MD_TOO_WEAK = 68; + /* Caller error */ + public static final int V_ERR_INVALID_CALL = 69; + /* Issuer lookup error */ + public static final int V_ERR_STORE_LOOKUP = 70; + /* Certificate transparency */ + //public static final int V_ERR_NO_VALID_SCTS = 71; + + /* Certificate verify flags */ + public static final int V_FLAG_CB_ISSUER_CHECK = 0x0 /* Deprecated */; public static final int V_FLAG_USE_CHECK_TIME = 0x2; public static final int V_FLAG_CRL_CHECK = 0x4; public static final int V_FLAG_CRL_CHECK_ALL = 0x8; public static final int V_FLAG_IGNORE_CRITICAL = 0x10; - public static final int V_FLAG_STRICT = 0x20; - public static final int V_FLAG_X509_STRICT = 0x20; + public static final int V_FLAG_X509_STRICT = 0x20; public static final int V_FLAG_ALLOW_PROXY_CERTS = 0x40; - public static final int V_FLAG_POLICY_CHECK = 0x80; - public static final int V_FLAG_EXPLICIT_POLICY = 0x100; + public static final int V_FLAG_POLICY_CHECK = 0x80; + public static final int V_FLAG_EXPLICIT_POLICY = 0x100; public static final int V_FLAG_INHIBIT_ANY = 0x200; - public static final int V_FLAG_INHIBIT_MAP = 0x400; - public static final int V_FLAG_NOTIFY_POLICY = 0x800; + public static final int V_FLAG_INHIBIT_MAP = 0x400; + public static final int V_FLAG_NOTIFY_POLICY = 0x800; + // NOTE: these aren't implemented: + /* Extended CRL features such as indirect CRLs, alternate CRL signing keys */ + //public static final int V_FLAG_EXTENDED_CRL_SUPPORT = 0x1000; + /* Delta CRL support */ + //public static final int V_FLAG_USE_DELTAS = 0x2000; + /* Check self-signed CA signature */ + public static final int V_FLAG_CHECK_SS_SIGNATURE = 0x4000; + /* Use trusted store first */ + public static final int V_FLAG_TRUSTED_FIRST = 0x8000; + + // ... + + /* Allow partial chains if at least one certificate is in trusted store */ + public static final int V_FLAG_PARTIAL_CHAIN = 0x80000; + /* + * If the initial chain is not trusted, do not attempt to build an alternative + * chain. Alternate chain checking was introduced in 1.1.0. Setting this flag + * will force the behaviour to match that of previous versions. + */ + public static final int V_FLAG_NO_ALT_CHAINS = 0x100000; + /* Do not check certificate/CRL validity against current time */ + public static final int V_FLAG_NO_CHECK_TIME = 0x200000; - public static final int VP_FLAG_DEFAULT = 0x1; - public static final int VP_FLAG_OVERWRITE = 0x2; - public static final int VP_FLAG_RESET_FLAGS = 0x4; - public static final int VP_FLAG_LOCKED = 0x8; - public static final int VP_FLAG_ONCE = 0x10; /* Internal use: mask of policy related options */ public static final int V_FLAG_POLICY_MASK = (V_FLAG_POLICY_CHECK | @@ -487,7 +535,7 @@ else if (maybeCertFile != null && new File(maybeCertFile).exists()) { public static final int X509_PURPOSE_MIN = 1; public static final int X509_PURPOSE_MAX = 8; - public static final int X509_TRUST_DEFAULT = -1; + public static final int X509_TRUST_DEFAULT = 0; /* Only valid in purpose settings */ public static final int X509_TRUST_COMPAT = 1; public static final int X509_TRUST_SSL_CLIENT = 2; @@ -496,12 +544,20 @@ else if (maybeCertFile != null && new File(maybeCertFile).exists()) { public static final int X509_TRUST_OBJECT_SIGN = 5; public static final int X509_TRUST_OCSP_SIGN = 6; public static final int X509_TRUST_OCSP_REQUEST = 7; + public static final int X509_TRUST_TSA = 8; public static final int X509_TRUST_MIN = 1; - public static final int X509_TRUST_MAX = 7; - - public static final int X509_TRUST_DYNAMIC = 1; - public static final int X509_TRUST_DYNAMIC_NAME = 2; + public static final int X509_TRUST_MAX = 8; + + /* trust_flags values */ + public static final int X509_TRUST_DYNAMIC = (1 << 0); + public static final int X509_TRUST_DYNAMIC_NAME = (1 << 1); + /* No compat trust if self-signed, preempts "DO_SS" */ + public static final int X509_TRUST_NO_SS_COMPAT = (1 << 2); + /* Compat trust if no explicit accepted trust EKUs */ + public static final int X509_TRUST_DO_SS_COMPAT = (1 << 3); + /* Accept "anyEKU" as a wildcard rejection OID and as a wildcard trust OID */ + public static final int X509_TRUST_OK_ANY_EKU = (1 << 4); public static final int X509_TRUST_TRUSTED = 1; public static final int X509_TRUST_REJECTED = 2; @@ -585,19 +641,22 @@ else if (maybeCertFile != null && new File(maybeCertFile).exists()) { public static final int ERR_R_DISABLED=(5|ERR_R_FATAL); public static final int EXFLAG_BCONS=0x1; - public static final int EXFLAG_KUSAGE=0x2; + //public static final int EXFLAG_KUSAGE=0x2; public static final int EXFLAG_XKUSAGE=0x4; - public static final int EXFLAG_NSCERT=0x8; + //public static final int EXFLAG_NSCERT=0x8; public static final int EXFLAG_CA=0x10; - public static final int EXFLAG_SS=0x20; + public static final int EXFLAG_SI=0x20; /* self-issued, maybe not self-signed */ public static final int EXFLAG_V1=0x40; - public static final int EXFLAG_INVALID=0x80; - public static final int EXFLAG_SET=0x100; - public static final int EXFLAG_CRITICAL=0x200; + //public static final int EXFLAG_INVALID=0x80; + /* EXFLAG_SET is set to indicate that some values have been precomputed */ + //public static final int EXFLAG_SET=0x100; + //public static final int EXFLAG_CRITICAL=0x200; public static final int EXFLAG_PROXY=0x400; - public static final int EXFLAG_INVALID_POLICY=0x400; + //public static final int EXFLAG_INVALID_POLICY=0x800; + //public static final int EXFLAG_FRESHEST=0x1000; + public static final int EXFLAG_SS=0x2000; /* cert is apparently self-signed */ public static final int XKU_SSL_SERVER=0x1; public static final int XKU_SSL_CLIENT=0x2; diff --git a/src/test/integration/Gemfile b/src/test/integration/Gemfile index 0211f6cb..0243c839 100644 --- a/src/test/integration/Gemfile +++ b/src/test/integration/Gemfile @@ -2,7 +2,5 @@ source 'https://rubygems.org' gem 'httpclient', :require => false gem 'trocla', '~> 0.2.3', :require => false - -group :development do - gem 'rake', '< 12', :require => nil # due JRuby 1.7 (1.9.3) -end \ No newline at end of file +gem 'faraday', '< 2.0', :require => false +gem 'manticore', '>= 0.8.0', :require => false diff --git a/src/test/integration/ssl_test.rb b/src/test/integration/ssl_test.rb index 7256ff9f..a81c1ec3 100644 --- a/src/test/integration/ssl_test.rb +++ b/src/test/integration/ssl_test.rb @@ -10,7 +10,9 @@ def test_connect_http_client_1 puts "------------------------------------------------------------" puts "-- HTTPClient.new.get 'https://www.bankofamerica.com'" puts "------------------------------------------------------------" - puts HTTPClient.new.get('https://www.bankofamerica.com') + res = HTTPClient.new.get('https://www.bankofamerica.com') + puts res if $VERBOSE + #assert_equal 200, res.code end def test_connect_http_client_2 @@ -20,22 +22,126 @@ def test_connect_http_client_2 puts "------------------------------------------------------------" puts "-- HTTPClient.new.get 'https://google.co.uk'" puts "------------------------------------------------------------" - puts HTTPClient.new.get('https://google.co.uk') + res = HTTPClient.new.get('https://google.co.uk') + puts res if $VERBOSE + #assert res.code < 400 end - def test_connect_net_http_1 - require 'uri'; require 'net/https' + def test_connect_net_http_base; require 'uri'; require 'net/https' + puts "\n" + puts "------------------------------------------------------------" + puts "-- Net::HTTP.new '#{url = 'https://rubygems.org'}'" + puts "------------------------------------------------------------" + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + res = http.get('/') + #assert_equal '200', res.code + end + + def test_connect_net_http_tls12; require 'uri'; require 'net/https' puts "\n" puts "------------------------------------------------------------" - puts "-- Net::HTTP.new ... 'https://rubygems.org'" + puts "-- Net::HTTP.new '#{url = 'https://fancyssl.hboeck.de'}'" puts "------------------------------------------------------------" - uri = URI.parse('https://rubygems.org') + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.ssl_version = :TLSv1_2 + res = http.get('/') + #assert_equal Net::HTTPOK, res.class + end + + # TODO https://www.howsmyssl.com/a/check + def test_connect_net_http_tls13; require 'uri'; require 'net/https' + puts "\n" + puts "------------------------------------------------------------" + puts "-- Net::HTTP.new '#{url = 'https://check-tls.akamai.io/v1/tlsinfo.json'}'" + puts "------------------------------------------------------------" + uri = URI.parse(url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true - puts http.get('/') + http.ssl_version = :TLSv1_3 + res = http.get'/', 'Accept' => 'application/json', 'User-Agent' => '' + #assert_equal '200', res.code + puts res.body + end + + # Pure Java TLS client - using HttpClient 4.x + def test_base_line_net_http_tls13; require 'manticore' + url = 'https://check-tls.akamai.io/v1/tlsinfo.json' + ssl_config = { + verify: :strict, + protocols: ['TLSv1.3'] + } + client = Manticore::Client.new(ssl: ssl_config) + response = client.get(url, headers: {}).call + assert response.code < 400 + puts response.body + # NOTE: ['TLSv1.3'] fails on older Java 8/11 versions, despite supporting + # - TLS_AES_128_GCM_SHA256 + # - TLS_AES_128_GCM_SHA256 + # + # "tls_sni_status": "present", + # "tls_version": "tls1.3", + # "tls_sni_value": "check-tls.akamai.io", + # "tls_cipher_name": "TLS_AES_256_GCM_SHA384", + # "output_version": "0.1.21", + # "timestamp": 1643716169 + # + # "tls_sni_status": "present", + # "tls_version": "tls1.2", + # "tls_sni_value": "check-tls.akamai.io", + # "tls_cipher_name": "ECDHE-RSA-AES256-GCM-SHA384", + # "client_ip": "86.49.15.48", + # "output_version": "0.1.21", + end if defined? JRUBY_VERSION + + def test_connect_net_http_other; require 'uri'; require 'net/https' + puts "\n" + puts "------------------------------------------------------------" + puts "-- Net::HTTP.new '#{url = 'https://s3.fr-par.scw.cloud'}'" + puts "------------------------------------------------------------" + + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + res = http.get('/') + #assert_equal Net::HTTPOK, res.class + end + + def test_faraday_get; require 'faraday' + puts "\n" + puts "------------------------------------------------------------" + puts "-- Faraday.get '#{url = 'https://httpbingo.org/ip'}'" + puts "------------------------------------------------------------" + + res = Faraday.get(url) + #assert_equal 200, res.status + puts res.body + end + + def test_connect_ssl_minmax_version; require 'openssl'; require 'socket' + puts "\n" + puts "------------------------------------------------------------" + puts "-- SSL min/max version ... 'https://google.co.uk'" + puts "------------------------------------------------------------" + + ctx = OpenSSL::SSL::SSLContext.new() + ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + client = TCPSocket.new('google.co.uk', 443) + ssl = OpenSSL::SSL::SSLSocket.new(client, ctx) + ssl.sync_close = true + ssl.connect + begin + assert_equal 'TLSv1.2', ssl.ssl_version + ensure + ssl.sysclose + end end end \ No newline at end of file diff --git a/src/test/java/org/jruby/ext/openssl/CipherTest.java b/src/test/java/org/jruby/ext/openssl/CipherTest.java index 51d7f81b..caed09ba 100644 --- a/src/test/java/org/jruby/ext/openssl/CipherTest.java +++ b/src/test/java/org/jruby/ext/openssl/CipherTest.java @@ -10,10 +10,10 @@ public class CipherTest { @Test public void ciphersGetLazyInitialized() { - assertTrue( Cipher.Algorithm.supportedCiphers.isEmpty() ); + //assertTrue( Cipher.Algorithm.AllSupportedCiphers.CIPHERS_MAP.isEmpty() ); assertFalse( Cipher.isSupportedCipher("UNKNOWN") ); - assertFalse( Cipher.Algorithm.supportedCiphers.isEmpty() ); - assertTrue( Cipher.Algorithm.supportedCiphers.get("DES") != null ); + assertFalse( Cipher.Algorithm.AllSupportedCiphers.CIPHERS_MAP.isEmpty() ); + assertTrue( Cipher.Algorithm.AllSupportedCiphers.CIPHERS_MAP.get("DES") != null ); assertTrue( Cipher.isSupportedCipher("DES") ); assertTrue( Cipher.isSupportedCipher("des") ); assertTrue( Cipher.isSupportedCipher("AES") ); @@ -36,14 +36,6 @@ public void jsseToOssl() { @Test public void osslToJsse() { - Cipher.Algorithm.supportedCiphers.clear(); - Cipher.Algorithm.supportedCiphersAll = false; - doTestOsslToJsse(); - } - - @Test - public void osslToJsseWithAllLoaded() { - Cipher.Algorithm.allSupportedCiphers(); doTestOsslToJsse(); } diff --git a/src/test/java/org/jruby/ext/openssl/SecurityHelperTest.java b/src/test/java/org/jruby/ext/openssl/SecurityHelperTest.java index f2254a16..a74f95b1 100644 --- a/src/test/java/org/jruby/ext/openssl/SecurityHelperTest.java +++ b/src/test/java/org/jruby/ext/openssl/SecurityHelperTest.java @@ -46,24 +46,6 @@ public void disableSecurityProvider() { SecurityHelper.setBouncyCastleProvider = false; } - @Test - public void injectCipherImpl() throws Exception { - SecurityHelper.addCipher("fake", CipherSpiFake.class); - javax.crypto.Cipher cipher = SecurityHelper.getCipher("fake"); - assertEquals(cipher.getProvider(), savedProvider); - java.lang.reflect.Field spi = cipher.getClass().getDeclaredField("spi"); - spi.setAccessible(true); - assertEquals(spi.get(cipher).getClass(), CipherSpiFake.class); - } - - @Test - public void injectSignatureImpl() throws Exception { - SecurityHelper.addSignature("fake", SignatureSpiFake.class); - Signature signature = SecurityHelper.getSignature("fake"); - assertEquals(signature.getProvider(), savedProvider); - assertEquals(signature.getClass(), SignatureSpiFake.class); - } - @Test public void usesBouncyCastleSecurityProviderByDefault() { assertNotNull(SecurityHelper.getSecurityProvider()); diff --git a/src/test/ruby/dsa/test_dsa.rb b/src/test/ruby/dsa/test_dsa.rb index 1c54fb57..3235777d 100644 --- a/src/test/ruby/dsa/test_dsa.rb +++ b/src/test/ruby/dsa/test_dsa.rb @@ -5,8 +5,35 @@ class TestDSA < TestCase def setup super - self.class.disable_security_restrictions! - require 'base64' + end + + def test_private + key = Fixtures.pkey("dsa1024") + 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_dup + key = Fixtures.pkey("dsa1024") + 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, key2.q, key2.g + 1) + assert_not_equal key.params, key2.params + #end end def test_dsa_param_accessors @@ -67,8 +94,175 @@ def test_dsa_sys_sign_verify doc = 'Sign ME!' digest = OpenSSL::Digest::SHA1.digest(doc) sig = dsa.syssign(digest) - puts sig.inspect if $VERBOSE + #puts sig.inspect if $VERBOSE assert dsa.sysverify(digest, sig).eql?(true) end + def test_DSAPrivateKey + # OpenSSL DSAPrivateKey format; similar to RSAPrivateKey + dsa512 = Fixtures.pkey("dsa512") + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(0), + OpenSSL::ASN1::Integer(dsa512.p), + OpenSSL::ASN1::Integer(dsa512.q), + OpenSSL::ASN1::Integer(dsa512.g), + OpenSSL::ASN1::Integer(dsa512.pub_key), + OpenSSL::ASN1::Integer(dsa512.priv_key) + ]) + key = OpenSSL::PKey::DSA.new(asn1.to_der) + assert_predicate key, :private? + assert_same_dsa dsa512, key + + pem = <<~EOF + -----BEGIN DSA PRIVATE KEY----- + MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok + RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D + AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR + S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ + Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S + 55jreJD3Se3slps= + -----END DSA PRIVATE KEY----- + EOF + key = OpenSSL::PKey::DSA.new(pem) + assert_same_dsa dsa512, key + + assert_equal asn1.to_der, dsa512.to_der + assert_equal pem, dsa512.export + end + + def test_DSAPrivateKey_encrypted + # key = abcdef + dsa512 = Fixtures.pkey("dsa512") + pem = <<~EOF + -----BEGIN DSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: AES-128-CBC,F8BB7BFC7EAB9118AC2E3DA16C8DB1D9 + + D2sIzsM9MLXBtlF4RW42u2GB9gX3HQ3prtVIjWPLaKBYoToRUiv8WKsjptfZuLSB + 74ZPdMS7VITM+W1HIxo/tjS80348Cwc9ou8H/E6WGat8ZUk/igLOUEII+coQS6qw + QpuLMcCIavevX0gjdjEIkojBB81TYDofA1Bp1z1zDI/2Zhw822xapI79ZF7Rmywt + OSyWzFaGipgDpdFsGzvT6//z0jMr0AuJVcZ0VJ5lyPGQZAeVBlbYEI4T72cC5Cz7 + XvLiaUtum6/sASD2PQqdDNpgx/WA6Vs1Po2kIUQIM5TIwyJI0GdykZcYm6xIK/ta + Wgx6c8K+qBAIVrilw3EWxw== + -----END DSA PRIVATE KEY----- + EOF + key = OpenSSL::PKey::DSA.new(pem, "abcdef") + assert_same_dsa dsa512, key + key = OpenSSL::PKey::DSA.new(pem) { "abcdef" } + assert_same_dsa dsa512, key + + cipher = OpenSSL::Cipher.new("aes-128-cbc") + exported = dsa512.to_pem(cipher, "abcdef\0\1") + assert_same_dsa dsa512, OpenSSL::PKey::DSA.new(exported, "abcdef\0\1") + assert_raise(OpenSSL::PKey::DSAError) { + OpenSSL::PKey::DSA.new(exported, "abcdef") + } + end + + def test_PUBKEY + dsa512 = Fixtures.pkey("dsa512") + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::ObjectId("DSA"), + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(dsa512.p), + OpenSSL::ASN1::Integer(dsa512.q), + OpenSSL::ASN1::Integer(dsa512.g) + ]) + ]), + OpenSSL::ASN1::BitString(OpenSSL::ASN1::Integer(dsa512.pub_key).to_der) + ]) + key = OpenSSL::PKey::DSA.new(asn1.to_der) + assert_not_predicate key, :private? + assert_same_dsa dup_public(dsa512), key + + ## + der = "0\x81\xF10\x81\xA8\x06\a*\x86H\xCE8\x04\x010\x81\x9C\x02A\x00\xE6Px\x1A\xF10\x8E\xBB\f\x94`\xEA\x1A\xCCkn\xA7\x85F\x1E\xA8\xF4\xE5\xAD\xE8X\x13b!\x04\x1D\xA3\x98\x86B1\xBFC\xA4E\x93\xC37\x03\x86\xF7\xDE\xE6\x0E0g\xBC2u\x8B\x9E\x8E\x99-\xCCm9\xE8\xBF\x02\x15\x00\x98.o\xF6\x83\xAD \xC1\xE06F\x1F\xB52j\xB7\x1A\e.\x83\x02@}\x1D\xD1p\xFF{]\xF0\xAE%\xD4VG\xBF\xB4\xC2e\xE2wd\xB6\xFBt]\xD3Z\x81\x94?M7D*\xA8\x8E-\xFF\x94wJ\xA1WS@\xDB\x91K\xF9\xBA\x0EP\xB0\x864c\x89\x0F\xE5\x05\x18\x02,'\x1A\x03D\x00\x02A\x00\x8C\xDF=\xD4\x90c\xCE\x93#\xE1\xB5\xA0\xB0\x00\xA62\b\x05\x7F\x9E\xC3KD\xE2\xFB\x9F\xBECUA\xF2\xA9\t7\xBA\xC0\xEF\xFD\x87\xAC$w\x81;\x165\xBDX\x84\v'\x16\xCA\x1EF\xC7\x02\xF8\xBCW\xA1x\xBD8" + pp OpenSSL::ASN1.decode(key.to_der) if $DEBUG + assert_equal der, key.to_der + + pem = <<-EOF +-----BEGIN PUBLIC KEY----- +MIHxMIGoBgcqhkjOOAQBMIGcAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgT +YiEEHaOYhkIxv0OkRZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB +4DZGH7UyarcaGy6DAkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqo +ji3/lHdKoVdTQNuRS/m6DlCwhjRjiQ/lBRgCLCcaA0QAAkEAjN891JBjzpMj4bWg +sACmMggFf57DS0Ti+5++Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxX +oXi9OA== +-----END PUBLIC KEY----- + EOF + key = OpenSSL::PKey::DSA.new(pem) + assert_same_dsa dup_public(dsa512), key + + ## + assert_equal der, key.to_der + assert_equal pem, key.export + + assert_equal der, dsa512.public_to_der + assert_equal der, key.public_to_der + assert_equal pem, dsa512.public_to_pem + assert_equal pem, key.public_to_pem + + dup_der = dup_public(dsa512).to_der + # pp OpenSSL::ASN1.decode(dup_der) + assert_equal asn1.to_der.size, dup_der.size + assert_equal asn1.to_der.encoding, dup_der.encoding + # TODO smt slightly weird with to_der: + #assert_equal asn1.to_der, dup_der + assert_equal asn1.value[1].value, OpenSSL::ASN1.decode(dup_der).value[1].value + assert_equal asn1.value[0].value[0].value, OpenSSL::ASN1.decode(dup_der).value[0].value[0].value + assert_equal asn1.value[0].value[1].value[0].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[0].value + assert_equal asn1.value[0].value[1].value[1].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[1].value + assert_equal asn1.value[0].value[1].value[2].value, OpenSSL::ASN1.decode(dup_der).value[0].value[1].value[2].value + + assert_equal pem, dup_public(dsa512).export + end if !defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1' # set_pqg only since Ruby 2.3 + + def test_read_DSAPublicKey_pem + # NOTE: 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 + + private + + def assert_same_dsa(expected, key) + check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) + end + + def check_component(base, test, keys) + keys.each { |comp| assert_equal base.send(comp), test.send(comp) } + end + + def dup_public(key) + case key + when OpenSSL::PKey::DSA + dsa = OpenSSL::PKey::DSA.new + dsa.set_pqg(key.p, key.q, key.g) + dsa.set_key(key.pub_key, nil) + dsa + else + raise "unknown key type: #{key.class}" + end + end + end diff --git a/src/test/ruby/ec/private_key_pkcs8.pem b/src/test/ruby/ec/private_key_pkcs8.pem new file mode 100644 index 00000000..d49cbf95 --- /dev/null +++ b/src/test/ruby/ec/private_key_pkcs8.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUmgU1rG7E9WJmB4A +D1RZ+PP+aYEH2ZZxWTGVR0gDr/qhRANCAAR5d0hOX+W8RznN62sAzIeozl4OBl6K +nKdpKKiZTAua05NCaWJR5mGnrCyn4g+sQV4pUgmp9NzSMwmXAzJt3GK9 +-----END PRIVATE KEY----- diff --git a/src/test/ruby/ec/test_ec.rb b/src/test/ruby/ec/test_ec.rb index 3ef98241..b2f3c315 100644 --- a/src/test/ruby/ec/test_ec.rb +++ b/src/test/ruby/ec/test_ec.rb @@ -3,6 +3,145 @@ class TestEC < TestCase + def test_ec_key + builtin_curves = OpenSSL::PKey::EC.builtin_curves + assert_not_empty builtin_curves + + builtin_curves.each do |curve_name, comment| + # Oakley curves and X25519 are not suitable for signing and causes + # FIPS-selftest failure on some environment, so skip for now. + next if ["Oakley", "X25519"].any? { |n| curve_name.start_with?(n) } + + key = OpenSSL::PKey::EC.generate(curve_name) + assert_predicate key, :private? + assert_predicate key, :public? + assert_nothing_raised { key.check_key } + end + + key1 = OpenSSL::PKey::EC.generate("prime256v1") + + # PKey is immutable in OpenSSL >= 3.0; constructing an empty EC object is deprecated + #if !openssl?(3, 0, 0) + key2 = OpenSSL::PKey::EC.new + key2.group = key1.group + key2.private_key = key1.private_key + key2.public_key = key1.public_key + assert_equal key1.to_der, key2.to_der + #end + + 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_generate + assert_raise(OpenSSL::PKey::ECError) { OpenSSL::PKey::EC.generate("non-existent") } + g = OpenSSL::PKey::EC::Group.new("prime256v1") + ec = OpenSSL::PKey::EC.generate(g) + assert_equal(true, ec.private?) + ec = OpenSSL::PKey::EC.generate("prime256v1") + assert_equal(true, ec.private?) + end + + def test_generate_key + ec = OpenSSL::PKey::EC.new("prime256v1") + assert_equal false, ec.private? + ec.generate_key! + assert_equal true, ec.private? + end #if !openssl?(3, 0, 0) + + def test_PUBKEY + p256 = Fixtures.pkey("p256") + p256pub = OpenSSL::PKey::EC.new(p256.public_to_der) + + public_to_der = "0Y0\x13\x06\a*\x86H\xCE=\x02\x01\x06\b*\x86H\xCE=\x03\x01\a\x03B\x00\x04\x16\td\xD9\xCF\xA8UB\nC\xAE\x1Edo[\x84\xB3OX\x1E\xE5I\x9F\xC0\xAC\xAE5xl\xB9\xC0\f\xD4\xFFA\xB9\xD5{m\t\xE0T\x97\xE3\x1A\x85\x9Bg\xF5\xF3\xB5$\xA7E\xE2\xA2fK\x7F]^zD6" + assert_equal public_to_der, p256.public_to_der + + # MRI: + uncompressed_public_key = "\x04\x16\td\xD9\xCF\xA8UB\nC\xAE\x1Edo[\x84\xB3OX\x1E\xE5I\x9F\xC0\xAC\xAE5xl\xB9\xC0\f\xD4\xFFA\xB9\xD5{m\t\xE0T\x97\xE3\x1A\x85\x9Bg\xF5\xF3\xB5$\xA7E\xE2\xA2fK\x7F]^zD6" + assert_equal uncompressed_public_key, p256.public_key.to_octet_string(:uncompressed) + + 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) + ) + ]) + assert_equal public_to_der, asn1.to_der + + to_der = "0w\x02\x01\x01\x04 \x80\xF8\xF4P\xEAq\xFDN\xD5\xE3\xBC\xB1\xA4\xE0\e\xBD\x14mt0\xF4Z\xB0\xB1\xE9b\x8A\xDD\x9AZ\x11\xF5\xA0\n\x06\b*\x86H\xCE=\x03\x01\a\xA1D\x03B\x00\x04\x16\td\xD9\xCF\xA8UB\nC\xAE\x1Edo[\x84\xB3OX\x1E\xE5I\x9F\xC0\xAC\xAE5xl\xB9\xC0\f\xD4\xFFA\xB9\xD5{m\t\xE0T\x97\xE3\x1A\x85\x9Bg\xF5\xF3\xB5$\xA7E\xE2\xA2fK\x7F]^zD6" + #pp OpenSSL::ASN1.decode(to_der) + # #>, + # #, + # #]>, + # #]>]> + + assert_equal to_der, p256.to_der + + 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_oid + key = OpenSSL::PKey::EC.new + assert_equal 'id-ecPublicKey', key.oid + end + def test_read_pem key_file = File.join(File.dirname(__FILE__), 'private_key.pem') @@ -32,6 +171,12 @@ def test_read_pem #pem = key.to_pem #puts pem #assert_equal(pem, OpenSSL::PKey::EC.new(pem).to_pem) + + # to_text + text = key.to_text + assert_include text, "Private-Key: (112 bit)\n" + assert_include text, "bb:54:b8:93:a9:51:3c:09:a6:37:f7:2c:95:2e\n" + assert_include text, "ASN1 OID: secp112r1\n" end def test_read_pem2 @@ -51,13 +196,59 @@ def test_read_pem2 #puts signature.inspect end + def test_read_pkcs8_with_ec + key_file = File.join(File.dirname(__FILE__), 'private_key_pkcs8.pem') + + key = OpenSSL::PKey::read(File.read(key_file)) + assert_equal OpenSSL::PKey::EC, key.class + assert_equal '37273549501637553234010607973347901861080883009977847480473501706546896416762', key.private_key.to_s + + assert_equal OpenSSL::PKey::EC::Point, key.public_key.class + public_key = '59992919564038617581477192805085606797983518604284049179473294859597640027055772972320536875319417493705914919226919250526441868144324498122209513139102397' + assert_equal public_key, key.public_key.to_bn.to_s + end + def test_point group = OpenSSL::PKey::EC::Group.new('prime256v1') client_public_key_bn = OpenSSL::BN.new('58089019511196532477248433747314139754458690644712400444716868601190212265537817278966641566813745621284958192417192818318052462970895792919572995957754854') binary = "\x04U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD\xBFB\x8FS\xCF\x13\x7F\x8C\x03N\x96\x9D&\xB2\xE1\xBDQ\b\xCE\x94!s\x06.\xC5?\x96\xC7q\xDA\x8B\xE6" - client_public_key = OpenSSL::PKey::EC::Point.new(group, client_public_key_bn) - assert_equal binary, client_public_key.to_bn.to_s(2) + point = OpenSSL::PKey::EC::Point.new(group, client_public_key_bn) + assert_equal binary, point.to_bn.to_s(2) + assert_equal binary, point.to_octet_string(:uncompressed) + + point2 = OpenSSL::PKey::EC::Point.new(group, point.to_octet_string(:uncompressed)) + assert_equal binary, point2.to_bn.to_s(2) + + compressed = "\x02U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD" + assert_equal compressed, point.to_octet_string(:compressed) + + # TODO: not yet implemented + # hybrid = "\x06U\x1D6|\xA9\x14\eC\x13\x99b\x96\x9B\x94f\x8F\xB0o\xE2\xD3\xBC%\x8E\xE0Xn\xF2|R\x99b\xBD\xBFB\x8FS\xCF\x13\x7F\x8C\x03N\x96\x9D&\xB2\xE1\xBDQ\b\xCE\x94!s\x06.\xC5?\x96\xC7q\xDA\x8B\xE6" + # assert_equal hybrid, point.to_octet_string(:hybrid) + end + + def test_point_error + assert_raise(ArgumentError) { OpenSSL::PKey::EC::Point.new } + assert_raise(TypeError) { OpenSSL::PKey::EC::Point.new(nil) } + assert_raise(TypeError) { OpenSSL::PKey::EC::Point.new(nil, '') } + assert_raise(TypeError) { OpenSSL::PKey::EC::Point.new(100, '') } + end + + def test_random_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) end require File.expand_path('base64.rb', File.dirname(__FILE__)) @@ -193,20 +384,14 @@ def convert16bit(key) def setup super - self.class.disable_security_restrictions! - - # @data1 = 'foo'; @data2 = 'bar' * 1000 # data too long for DSA sig @groups = []; @keys = [] OpenSSL::PKey::EC.builtin_curves.each do |curve, comment| next if curve.start_with?("Oakley") # Oakley curves are not suitable for ECDSA - group = OpenSSL::PKey::EC::Group.new(curve) - key = OpenSSL::PKey::EC.new(group) - key.generate_key - - @groups << group; @keys << key + @groups << group = OpenSSL::PKey::EC::Group.new(curve) + @keys << OpenSSL::PKey::EC.generate(group) end end @@ -233,6 +418,21 @@ def test_check_key end end + def test_sign_verify + p256 = Fixtures.pkey("p256") + data = "Sign me!" + signature = p256.sign("SHA1", data) + assert_equal true, p256.verify("SHA1", signature, data) + + signature0 = (<<~'end;').unpack("m")[0] + MEQCIEOTY/hD7eI8a0qlzxkIt8LLZ8uwiaSfVbjX2dPAvN11AiAQdCYx56Fq + QdBp1B4sxJoA8jvODMMklMyBKVmudboA6A== + end; + assert_equal true, p256.verify("SHA256", signature0, data) + signature1 = signature0.succ + assert_equal false, p256.verify("SHA256", signature1, data) + end + def test_group_encoding for group in @groups for meth in [:to_der, :to_pem] @@ -278,13 +478,149 @@ def test_set_keys end end if false # NOT-IMPLEMENTED TODO - def test_dsa_sign_verify - data1 = 'foo' + def test_dsa_sign_verify_all + data1 = 'hashed-value' for key in @keys + next if key.group.curve_name == 'SM2' + sig = key.dsa_sign_asn1(data1) - assert(key.dsa_verify_asn1(data1, sig)) + assert_equal(true, key.dsa_verify_asn1(data1, sig)) + assert_equal(false, key.dsa_verify_asn1(data1 + 'X', sig)) end - end if false # NOT-IMPLEMENTED + end + + 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_raise(OpenSSL::PKey::ECError) { key.dsa_verify_asn1(data1, malformed_sig) } + end + + def test_new_from_der + priv_key_hex = '05768F097A19FFE5022D4A862CDBAE22019695D1C2F88FD41607417AD45E2F55' + pub_key_hex = '04B827833DC1BC38CE0BBE36E0357B1D08AB0BFA05DBD211F0FC677FF9913FAF0EB3A3CC562EEAE8D841B112DBFDAD494E10CFBD4964DC2D175D06F17ACC5771CF' + do_test_from_sequence('prime256v1', pub_key_hex, priv_key_hex) + do_test_from_sequence('prime256v1', pub_key_hex, nil) + + priv_key_hex = 'D4E775192298037DAD55150AE76C8585CE4AD628897F5F9F02762C416F1D4A33' + pub_key_hex = '0456D3DD1587DE605D167AB037FF9856B58705970BA3AE49E68CDFA8A5D580EC506E1D6F1AEFE5621EF458322F68C59D461FC5D3633881D82BD8E4AF7924306979' + do_test_from_sequence('prime256v1', pub_key_hex, priv_key_hex) + do_test_from_sequence('prime256v1', pub_key_hex, nil) + + priv_key_hex = '9174C3E24DBA2AADE34D4885371B0AEA89D44CFEC70348C9FF5EB3207550F18A' + pub_key_hex = '048344CA3520410CFD1D77FEA79AF543A2769545D6D143A12E86AC8F65D9280049FDC88A883D748C6229D9210AD0984DD4ED8F7742ECC0588409446FF6BC8830AA' + do_test_from_sequence('prime256v1', pub_key_hex, priv_key_hex) + do_test_from_sequence('prime256v1', pub_key_hex, nil) + end + + def do_test_from_sequence(curve, pub_key_hex, priv_key_hex) + group = OpenSSL::PKey::EC::Group.new(curve) + d = OpenSSL::BN.new(priv_key_hex, 16) if priv_key_hex # private_key + point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(pub_key_hex, 16)) # public_key (x, y) + + sequence = if priv_key_hex + # https://datatracker.ietf.org/doc/html/rfc5915.html + # ECPrivateKey ::= SEQUENCE { + # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + # privateKey OCTET STRING, + # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + # publicKey [1] BIT STRING OPTIONAL + # } + + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(1), + OpenSSL::ASN1::OctetString(d.to_s(2)), + OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT) + ]) + else + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) + ]) + end + + key = OpenSSL::PKey::EC.new(sequence.to_der) + assert_equal group.curve_name, key.group.curve_name + assert_equal group, key.group + assert_equal point, key.public_key + assert_equal d, key.private_key if d + end + private :do_test_from_sequence + + def test_new_from_der_jwt_style + jwk_x = "g0TKNSBBDP0dd_6nmvVDonaVRdbRQ6EuhqyPZdkoAEk" + jwk_y = "_ciKiD10jGIp2SEK0JhN1O2Pd0LswFiECURv9ryIMKo" + do_test_from_sequence_with_packed_point('prime256v1', jwk_x, jwk_y) + + jwk_x = "ts5_Jv5_QkWPVkaC_Y7rGZ2gdeJSkDR3I96M2CuVNtU" + jwk_y = "fCLLRp7lX_Q8g60IRhT8sNONGTjNmoTWUny8FPe91Gs" + do_test_from_sequence_with_packed_point('prime256v1', jwk_x, jwk_y) + + jwk_x = "iIv_aqVNBfTBr3C7u8E3kYrWbYXjHnH9jLzbBkl1PqA" + jwk_y = "sXueAB7o9QmrmDQGGy7hqN0bx5gOxYDJyLQAMDNMRBw" + jwk_d = "-KgbRgiVEztCTgxSZmScegXkBVNokqZlCodlpakFtFc" + do_test_from_sequence_with_packed_point('prime256v1', jwk_x, jwk_y, jwk_d) + do_test_from_sequence_with_packed_point('prime256v1', jwk_x, jwk_y) + + jwk_x = "mAObq2aOmjkZwS5ruLmZITbXKTepItbnyrMm1VWGeeg" + jwk_y = "EtQDulK7N-v_0mdbFQe-bNCyc-ey1sPRa1l--_7vAiA" + do_test_from_sequence_with_packed_point('prime256v1', jwk_x, jwk_y) # GH-318 + end + + def do_test_from_sequence_with_packed_point(curve, jwk_x, jwk_y, jwk_d = nil) + group = OpenSSL::PKey::EC::Group.new(curve) + d = OpenSSL::BN.new(decode_octets(jwk_d), 2) if jwk_d + + x_octets = decode_octets(jwk_x) + y_octets = decode_octets(jwk_y) + + point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new([0x04, x_octets, y_octets].pack('Ca*a*'), 2)) + + sequence = if jwk_d + # https://datatracker.ietf.org/doc/html/rfc5915.html + # ECPrivateKey ::= SEQUENCE { + # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), + # privateKey OCTET STRING, + # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, + # publicKey [1] BIT STRING OPTIONAL + # } + + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(1), + OpenSSL::ASN1::OctetString(OpenSSL::BN.new(decode_octets(jwk_d), 2).to_s(2)), + OpenSSL::ASN1::ObjectId(curve, 0, :EXPLICIT), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT) + ]) + else + OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Sequence([OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(curve)]), + OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed)) + ]) + end + + key = OpenSSL::PKey::EC.new(sequence.to_der) + assert_equal group.curve_name, key.group.curve_name + assert_equal group, key.group + assert_equal point, key.public_key + assert_equal d, key.private_key if d + end + private :do_test_from_sequence + + def decode_octets(base64_encoded_coordinate); require 'base64' + bytes = ::Base64.urlsafe_decode64(base64_encoded_coordinate) + assert_false bytes.bytesize.odd? + bytes + end + private :decode_octets # def test_dh_compute_key # for key in @keys @@ -299,4 +635,19 @@ def test_dsa_sign_verify # end # end -end \ No newline at end of file + 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 + + def check_component(base, test, keys) + keys.each { |comp| + assert_equal base.send(comp), test.send(comp) + } + end +end diff --git a/src/test/ruby/fixtures/pkey/custom/ec256-private-v2.pem b/src/test/ruby/fixtures/pkey/custom/ec256-private-v2.pem new file mode 100644 index 00000000..15793bee --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec256-private-v2.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFZpgytOAXPVreqGsHPdD9pojw30bnlqfUAqFZ3V3/qeoAoGCCqGSM49 +AwEHoUQDQgAE7JbAf3pWEEPje6NG+4dGOwIZnNwRFIe7DnQ4xFWKPrL5tVWlBh7N +DFhjGNhiyO+aQjbcx9uWV74ifq7i21Bemg== +-----END EC PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/ec256-public-v2.pem b/src/test/ruby/fixtures/pkey/custom/ec256-public-v2.pem new file mode 100644 index 00000000..a38495d5 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec256-public-v2.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7JbAf3pWEEPje6NG+4dGOwIZnNwR +FIe7DnQ4xFWKPrL5tVWlBh7NDFhjGNhiyO+aQjbcx9uWV74ifq7i21Bemg== +-----END PUBLIC KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/ec256k-private.pem b/src/test/ruby/fixtures/pkey/custom/ec256k-private.pem new file mode 100644 index 00000000..76bcd12c --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec256k-private.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIMTine3s8tT+8bswDM4/z8o+wIYGb9PQPrw8x6Nu6QDdoAcGBSuBBAAK +oUQDQgAEy8wuv6+fXodLPLfhxm132y1R8m4dkng7tHe7N+sULV2Eth6AxEXQfd+E +4nuceR21UNCvQKqxiYwCzVwIKcHe/A== +-----END EC PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/ec256k-public.pem b/src/test/ruby/fixtures/pkey/custom/ec256k-public.pem new file mode 100644 index 00000000..21eca675 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec256k-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy8wuv6+fXodLPLfhxm132y1R8m4dkng7 +tHe7N+sULV2Eth6AxEXQfd+E4nuceR21UNCvQKqxiYwCzVwIKcHe/A== +-----END PUBLIC KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/ec512-private.pem b/src/test/ruby/fixtures/pkey/custom/ec512-private.pem new file mode 100644 index 00000000..753317a5 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec512-private.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIB0/+ffxEj7j62xvGaB5pvzk888e412ESO/EK/K0QlS9dSF8+Rj1rG +zqpRB8fvDnoe8xdmkW/W5GKzojMyv7YQYumgBwYFK4EEACOhgYkDgYYABAEw74Yw +aTbPY6TtWmxx6LJDzCX2nKWCPnKdZcEH9Ncu8g5RjRBRq2yacja3OoS6nA2YeDng +reBJxZr376P6Ns6XcQFWDA6K/MCTrEBCsPxXZNxd8KR9vMGWhgNtWRrcKzwJfQkr +suyehZkbbYyFnAWyARKHZuV7VUXmeEmRS/f93MPqVA== +-----END EC PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/ec512-public.pem b/src/test/ruby/fixtures/pkey/custom/ec512-public.pem new file mode 100644 index 00000000..a74a57b0 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/ec512-public.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBMO+GMGk2z2Ok7VpsceiyQ8wl9pyl +gj5ynWXBB/TXLvIOUY0QUatsmnI2tzqEupwNmHg54K3gScWa9++j+jbOl3EBVgwO +ivzAk6xAQrD8V2TcXfCkfbzBloYDbVka3Cs8CX0JK7LsnoWZG22MhZwFsgESh2bl +e1VF5nhJkUv3/dzD6lQ= +-----END PUBLIC KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/rsa-2048-private.pem b/src/test/ruby/fixtures/pkey/custom/rsa-2048-private.pem new file mode 100644 index 00000000..dab50df2 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/rsa-2048-private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA4GzZTLU48c4WbyvHi+QKrB71x+T0eq5hqDbQqnlYjhD1Ika7 +io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lgCZr4+8UHbr1qf0Eu5HZSpszs2YxY +8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldHH/eazwnc3F/hgNsV0xjScVilejgo +cJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y0HFyrMsPoQyLuCUpr6ulOfrkr7ZO +dhAIG8r1HcjOp/AUjM15vfXcbUZjkM/VloifX1YitU3upMGJ8/DpFGffMOImrn5r +6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgVQQIDAQABAoIBAEH0Ozgr2fxWEInD +V/VooypKPvjr9F1JejGxSkmPN9MocKIOH3dsbZ1uEXa3ItBUxan4XlK06SNgp+tH +xULfF/Y6sQlsse59hBq50Uoa69dRShn1AP6JgZVvkduMPBNxUYL5zrs6emsQXb9Q +DglDRQfEAJ7vyxSIqQDxYcyT8uSUF70dqFe+E9B2VE3D6ccHc98k41pJrAFAUFH1 +wwvDhfyYr7/Ultut9wzpZvU1meF3Vna3GOUHfxrG6wu1G+WIWHGjouzThsc1qiVI +BtMCJxuCt5fOXRbU4STbMqhB6sZHiOh6J/dZU6JwRYt+IS8FB6kCNFSEWZWQledJ +XqtYSQECgYEA9nmnFTRj3fTBq9zMXfCRujkSy6X2bOb39ftNXzHFuc+I6xmv/3Bs +P9tDdjueP/SnCb7i/9hXkpEIcxjrjiqgcvD2ym1hE4q+odMzRAXYMdnmzI34SVZE +U5hYJcYsXNKrTTleba7QgqdORmyJ9FwqLO40udvmrZMY223XDwgRkOkCgYEA6RkO +5wjjrWWp/G1YN3KXZTS1m2/eGrUThohXKAfAjbWWiouNLW2msXrxEWsPRL6xKiHu +X9cwZwzi3MstAgk+bphUGUVUkGKNDjWHJA25tDYjbPtkd6xbL4eCHsKpNL3HNYr9 +N0CIvgn7qjaHRBem0iK7T6keY4axaSVddEwYapkCgYEA13K5qaB1F4Smcpt8DTWH +vPe8xUUaZlFzOJLmLCsuwmB2N8Ppg2j7RspcaxJsH021YaB5ftjWm+ipMSr8ZPY/ +8JlPsNzxuYpTXtNmAbT2KYVm6THEch61dTk6/DIBf1YrpUJbl5by7vJeStL/uBmE +SGgksL5XIyzs0opuLdaIvFkCgYAyBLWE8AxjFfCvAQuwAj/ocLITo6KmWnrRIIqL +RXaVMgUWv7FQsTnW1cnK8g05tC2yG8vZ9wQk6Mf5lwOWb0NdWgSZ0528ydj41pWk +L+nMeN2LMjqxz2NVxJ8wWJcUgTCxFZ0WcRumo9/D+6V1ABpE9zz4cBLcSnfhVypB +nV6T6QKBgQCSZNCQ9HPxjAgYcsqc5sjNwuN1GHQZSav3Tye3k6zHENe1lsteT9K8 +xciGIuhybKZBvB4yImIIHCtnH+AS+mHAGqHarjNDMfvjOq0dMibPx4+bkIiHdBIH +Xz+j5kmntvFiUnzr0Z/Tcqo+r8FvyCo1YWgwqGP8XoFrswD7gy7cZw== +-----END RSA PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/custom/rsa-2048-public.pem b/src/test/ruby/fixtures/pkey/custom/rsa-2048-public.pem new file mode 100644 index 00000000..c80a9523 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/custom/rsa-2048-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GzZTLU48c4WbyvHi+QK +rB71x+T0eq5hqDbQqnlYjhD1Ika7io1iplsdJWJuyxfYbUkb2Ol0fj4koZ/GS6lg +CZr4+8UHbr1qf0Eu5HZSpszs2YxY8U5RHnrpw67co7hlgAR9HbyNf5XIYgLV9ldH +H/eazwnc3F/hgNsV0xjScVilejgocJ4zcsyymvW8t42lteM7bI867ZuJhGop/V+Y +0HFyrMsPoQyLuCUpr6ulOfrkr7ZOdhAIG8r1HcjOp/AUjM15vfXcbUZjkM/Vloif +X1YitU3upMGJ8/DpFGffMOImrn5r6BT494V8rRyN2qvQoAkLJpqZ0avLxwiR2lgV +QQIDAQAB +-----END PUBLIC KEY----- diff --git a/src/test/ruby/fixtures/pkey/dsa1024 b/src/test/ruby/fixtures/pkey/dsa1024 new file mode 100644 index 00000000..1bf49889 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/dsa1024 @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCH9aAoXvWWThIjkA6D+nI1F9ksF9iDq594rkiGNOT9sPDOdB+n +D+qeeeeloRlj19ymCSADPI0ZLRgkchkAEnY2RnqnhHOjVf/roGgRbW+iQDMbQ9wa +/pvc6/fAbsu1goE1hBYjm98/sZEeXavj8tR56IXnjF1b6Nx0+sgeUKFKEQIVAMiz +4BJUFeTtddyM4uadBM7HKLPRAoGAZdLBSYNGiij7vAjesF5mGUKTIgPd+JKuBEDx +OaBclsgfdoyoF/TMOkIty+PVlYD+//Vl2xnoUEIRaMXHwHfm0r2xUX++oeRaSScg +YizJdUxe5jvBuBszGPRc/mGpb9YvP0sB+FL1KmuxYmdODfCe51zl8uM/CVhouJ3w +DjmRGscCgYAuFlfC7p+e8huCKydfcv/beftqjewiOPpQ3u5uI6KPCtCJPpDhs3+4 +IihH2cPsAlqwGF4tlibW1+/z/OZ1AZinPK3y7b2jSJASEaPeEltVzB92hcd1khk2 +jTYcmSsV4VddplOPK9czytR/GbbibxsrhhgZUbd8LPbvIgaiadJ1PgIUBnJ/5vN2 +CVArsEzlPUCbohPvZnE= +-----END DSA PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/dsa256 b/src/test/ruby/fixtures/pkey/dsa256 new file mode 100644 index 00000000..61dadf18 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/dsa256 @@ -0,0 +1,8 @@ +-----BEGIN DSA PRIVATE KEY----- +MIH3AgEAAkEAhk2libbY2a8y2Pt21+YPYGZeW6wzaW2yfj5oiClXro9XMR7XWLkE +9B7XxLNFCS2gmCCdMsMW1HulaHtLFQmB2wIVAM43JZrcgpu6ajZ01VkLc93gu/Ed +AkAOhujZrrKV5CzBKutKLb0GVyVWmdC7InoNSMZEeGU72rT96IjM59YzoqmD0pGM +3I1o4cGqg1D1DfM1rQlnN1eSAkBq6xXfEDwJ1mLNxF6q8Zm/ugFYWR5xcX/3wFiT +b4+EjHP/DbNh9Vm5wcfnDBJ1zKvrMEf2xqngYdrV/3CiGJeKAhRvL57QvJZcQGvn +ISNX5cMzFHRW3Q== +-----END DSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/test/ruby/fixtures/pkey/dsa512 b/src/test/ruby/fixtures/pkey/dsa512 new file mode 100644 index 00000000..962c41cc --- /dev/null +++ b/src/test/ruby/fixtures/pkey/dsa512 @@ -0,0 +1,8 @@ +-----BEGIN DSA PRIVATE KEY----- +MIH4AgEAAkEA5lB4GvEwjrsMlGDqGsxrbqeFRh6o9OWt6FgTYiEEHaOYhkIxv0Ok +RZPDNwOG997mDjBnvDJ1i56OmS3MbTnovwIVAJgub/aDrSDB4DZGH7UyarcaGy6D +AkB9HdFw/3td8K4l1FZHv7TCZeJ3ZLb7dF3TWoGUP003RCqoji3/lHdKoVdTQNuR +S/m6DlCwhjRjiQ/lBRgCLCcaAkEAjN891JBjzpMj4bWgsACmMggFf57DS0Ti+5++ +Q1VB8qkJN7rA7/2HrCR3gTsWNb1YhAsnFsoeRscC+LxXoXi9OAIUBG98h4tilg6S +55jreJD3Se3slps= +-----END DSA PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/p256 b/src/test/ruby/fixtures/pkey/p256 new file mode 100644 index 00000000..97c97d9f --- /dev/null +++ b/src/test/ruby/fixtures/pkey/p256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIID49FDqcf1O1eO8saTgG70UbXQw9Fqwseliit2aWhH1oAoGCCqGSM49 +AwEHoUQDQgAEFglk2c+oVUIKQ64eZG9bhLNPWB7lSZ/ArK41eGy5wAzU/0G51Xtt +CeBUl+MahZtn9fO1JKdF4qJmS39dXnpENg== +-----END EC PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/rsa1024 b/src/test/ruby/fixtures/pkey/rsa1024 new file mode 100644 index 00000000..464de074 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/rsa1024 @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx +aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ +Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB +AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 +maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T +gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 +74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE +JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX +sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII +8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA +wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi +qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD +dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== +-----END RSA PRIVATE KEY----- diff --git a/src/test/ruby/fixtures/pkey/rsa2048 b/src/test/ruby/fixtures/pkey/rsa2048 new file mode 100644 index 00000000..ac89cd88 --- /dev/null +++ b/src/test/ruby/fixtures/pkey/rsa2048 @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN +s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign +4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D +kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl +NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J +DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb +I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq +PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V +seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0 +Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc +VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW +wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G +0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj +XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb +aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n +h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw +Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k +IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb +v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId +U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr +vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS +Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC +9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41 +gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG +4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw== +-----END RSA PRIVATE KEY----- diff --git a/src/test/ruby/oaep/test_oaep.rb b/src/test/ruby/oaep/test_oaep.rb index 2adeaafb..4d4109f5 100644 --- a/src/test/ruby/oaep/test_oaep.rb +++ b/src/test/ruby/oaep/test_oaep.rb @@ -5,7 +5,7 @@ class TestOaep < TestCase def setup super - self.class.disable_security_restrictions! + require 'base64' end diff --git a/src/test/ruby/pkcs7/test_attribute.rb b/src/test/ruby/pkcs7/test_attribute.rb index 0f768f3d..9e4d8ea9 100644 --- a/src/test/ruby/pkcs7/test_attribute.rb +++ b/src/test/ruby/pkcs7/test_attribute.rb @@ -3,13 +3,10 @@ module PKCS7Test class TestAttribute < TestCase - OctetString = org.bouncycastle.asn1.DEROctetString - Attribute = org.jruby.ext.openssl.impl.Attribute - def test_attributes - val = OctetString.new("foo".to_java_bytes) - val2 = OctetString.new("bar".to_java_bytes) - attr = Attribute.create(123, 444, val) + val = org.bouncycastle.asn1.DEROctetString.new("foo".to_java_bytes) + val2 = org.bouncycastle.asn1.DEROctetString.new("bar".to_java_bytes) + attr = org.jruby.ext.openssl.impl.Attribute.create(123, 444, val) assert_raise NoMethodError do attr.type = 12 end @@ -20,12 +17,12 @@ def test_attributes assert_equal 123, attr.type assert_equal val, attr.set.get(0) - attr2 = Attribute.create(123, 444, val) + attr2 = org.jruby.ext.openssl.impl.Attribute.create(123, 444, val) assert_equal attr, attr2 - assert_not_equal Attribute.create(124, 444, val), attr - assert_not_equal Attribute.create(123, 444, val2), attr + assert_not_equal org.jruby.ext.openssl.impl.Attribute.create(124, 444, val), attr + assert_not_equal org.jruby.ext.openssl.impl.Attribute.create(123, 444, val2), attr end end diff --git a/src/test/ruby/pkcs7/test_bio.rb b/src/test/ruby/pkcs7/test_bio.rb index f6ed0725..e7f344e0 100644 --- a/src/test/ruby/pkcs7/test_bio.rb +++ b/src/test/ruby/pkcs7/test_bio.rb @@ -1,12 +1,10 @@ -require File.expand_path('../pkcs7_helper', File.dirname(__FILE__)) +require File.expand_path('../test_helper', File.dirname(__FILE__)) module PKCS7Test class TestBIO < TestCase - def setup; require 'jopenssl/load' end - def test_string_bio_simple - bio = BIO::from_string("abc") + bio = org.jruby.ext.openssl.impl.BIO::from_string("abc") arr = Java::byte[20].new read = bio.gets(arr, 10) assert_equal 3, read @@ -14,7 +12,7 @@ def test_string_bio_simple end def test_string_bio_simple_with_newline - bio = BIO::from_string("abc\n") + bio = org.jruby.ext.openssl.impl.BIO::from_string("abc\n") arr = Java::byte[20].new read = bio.gets(arr, 10) assert_equal 4, read @@ -22,7 +20,7 @@ def test_string_bio_simple_with_newline end def test_string_bio_simple_with_newline_and_more_data - bio = BIO::from_string("abc\nfoo\n\nbar") + bio = org.jruby.ext.openssl.impl.BIO::from_string("abc\nfoo\n\nbar") arr = Java::byte[20].new read = bio.gets(arr, 10) assert_equal 4, read diff --git a/src/test/ruby/pkcs7/test_mime.rb b/src/test/ruby/pkcs7/test_mime.rb index 79384cf5..59218377 100644 --- a/src/test/ruby/pkcs7/test_mime.rb +++ b/src/test/ruby/pkcs7/test_mime.rb @@ -1,7 +1,15 @@ -require File.expand_path('../pkcs7_helper', File.dirname(__FILE__)) +require File.expand_path('../test_helper', File.dirname(__FILE__)) module PKCS7Test class TestMIME < TestCase + + require 'jopenssl/load' + + java_import 'org.jruby.ext.openssl.impl.BIO' + java_import 'org.jruby.ext.openssl.impl.Mime' + java_import 'org.jruby.ext.openssl.impl.MimeParam' + java_import 'org.jruby.ext.openssl.impl.MimeHeader' + def test_find_header_returns_null_on_nonexisting_header headers = [] assert_nil Mime::DEFAULT.find_header(headers, "foo") diff --git a/src/test/ruby/pkcs7/test_pkcs7.rb b/src/test/ruby/pkcs7/test_pkcs7.rb index 6db6891a..8b0e316b 100644 --- a/src/test/ruby/pkcs7/test_pkcs7.rb +++ b/src/test/ruby/pkcs7/test_pkcs7.rb @@ -1,8 +1,23 @@ # coding: US-ASCII -require File.expand_path('../pkcs7_helper', File.dirname(__FILE__)) +require File.expand_path('../test_helper', File.dirname(__FILE__)) module PKCS7Test class TestPKCS7 < TestCase + + require 'jopenssl/load' + + require File.expand_path('../pkcs7_helper', File.dirname(__FILE__)) + + java_import 'org.jruby.ext.openssl.impl.PKCS7' + java_import 'org.jruby.ext.openssl.impl.ASN1Registry' + + java_import 'java.math.BigInteger' + java_import 'javax.crypto.Cipher' + + java_import 'org.bouncycastle.asn1.x509.AlgorithmIdentifier' + + OctetString = org.bouncycastle.asn1.DEROctetString + def test_is_signed p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signed @@ -73,7 +88,7 @@ def test_set_detached p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signed - sign = Signed.new + sign = org.jruby.ext.openssl.impl.Signed.new p7.sign = sign test_p7 = PKCS7.new @@ -90,7 +105,7 @@ def test_set_not_detached p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signed - sign = Signed.new + sign = org.jruby.ext.openssl.impl.Signed.new p7.sign = sign test_p7 = PKCS7.new @@ -108,7 +123,7 @@ def test_is_detached p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signed - sign = Signed.new + sign = org.jruby.ext.openssl.impl.Signed.new p7.sign = sign test_p7 = PKCS7.new @@ -160,8 +175,6 @@ def test_set_type_signed assert_nil p7.get_other end - OctetString = org.bouncycastle.asn1.DEROctetString - def test_set_type_data p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_data @@ -272,7 +285,7 @@ def test_set_cipher_on_enveloped_object p7.type = ASN1Registry::NID_pkcs7_enveloped c = javax.crypto.Cipher.getInstance("RSA") - cipher = CipherSpec.new(c, "RSA", 128) + cipher = org.jruby.ext.openssl.impl.CipherSpec.new(c, "RSA", 128) p7.cipher = cipher @@ -285,7 +298,7 @@ def test_set_cipher_on_signed_and_enveloped_object p7.type = ASN1Registry::NID_pkcs7_signedAndEnveloped c = javax.crypto.Cipher.getInstance("RSA") - cipher = CipherSpec.new(c, "RSA", 128) + cipher = org.jruby.ext.openssl.impl.CipherSpec.new(c, "RSA", 128) p7.cipher = cipher @@ -343,25 +356,25 @@ def test_add_signer_to_something_that_cant_have_signers p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_enveloped assert_raise_pkcs7_exception do - p7.add_signer(SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) + p7.add_signer(org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) end p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_data assert_raise_pkcs7_exception do - p7.add_signer(SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) + p7.add_signer(org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) end p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_encrypted assert_raise_pkcs7_exception do - p7.add_signer(SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) + p7.add_signer(org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) end p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_digest assert_raise_pkcs7_exception do - p7.add_signer(SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) + p7.add_signer(org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil)) end end @@ -369,7 +382,7 @@ def test_add_signer_to_signed_should_add_that_to_stack p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signed - si = SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil) + si = org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil) p7.add_signer(si) assert_equal 1, p7.get_sign.signer_info.size @@ -381,7 +394,7 @@ def test_add_signer_to_signed_and_enveloped_should_add_that_to_stack p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_signedAndEnveloped - si = SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil) + si = org.jruby.ext.openssl.impl.SignerInfoWithPkey.new(nil, nil, nil, nil, nil, nil, nil) p7.add_signer(si) assert_equal 1, p7.get_signed_and_enveloped.signer_info.size @@ -391,15 +404,15 @@ def test_add_signer_to_signed_and_enveloped_should_add_that_to_stack BIG_ONE = BigInteger::ONE.to_java def create_signer_info_with_algo(algo) - md5 = AlgorithmIdentifier.new(ASN1Registry.nid2obj(4)) - SignerInfoWithPkey.new( - ASN1Integer.new(BIG_ONE), - IssuerAndSerialNumber.new(X500Name.new("C=SE"), BIG_ONE), + md5 = algorithm_identifier(4) + org.jruby.ext.openssl.impl.SignerInfoWithPkey.new( + org.bouncycastle.asn1.ASN1Integer.new(BIG_ONE), + org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber.new(org.bouncycastle.asn1.x500.X500Name.new("C=SE"), BIG_ONE), algo, - DERSet.new, + org.bouncycastle.asn1.DERSet.new, md5, - DEROctetString.new([].to_java(:byte)), - DERSet.new + org.bouncycastle.asn1.DEROctetString.new([].to_java(:byte)), + org.bouncycastle.asn1.DERSet.new ) end @@ -408,8 +421,8 @@ def test_add_signer_to_signed_with_new_algo_should_add_that_algo_to_the_algo_lis p7.type = ASN1Registry::NID_pkcs7_signed # YES, these numbers are correct. Don't change them. They are OpenSSL internal NIDs - md5 = AlgorithmIdentifier.new(ASN1Registry.nid2obj(4)) - md4 = AlgorithmIdentifier.new(ASN1Registry.nid2obj(5)) + md5 = algorithm_identifier(4) + md4 = algorithm_identifier(5) si = create_signer_info_with_algo(md5) p7.add_signer(si) @@ -437,8 +450,8 @@ def test_add_signer_to_signed_and_enveloped_with_new_algo_should_add_that_algo_t p7.type = ASN1Registry::NID_pkcs7_signedAndEnveloped # YES, these numbers are correct. Don't change them. They are OpenSSL internal NIDs - md5 = AlgorithmIdentifier.new(ASN1Registry.nid2obj(4)) - md4 = AlgorithmIdentifier.new(ASN1Registry.nid2obj(5)) + md5 = algorithm_identifier(4) + md4 = algorithm_identifier(5) si = create_signer_info_with_algo(md5) p7.add_signer(si) @@ -460,6 +473,13 @@ def test_add_signer_to_signed_and_enveloped_with_new_algo_should_add_that_algo_t assert p7.get_signed_and_enveloped.md_algs.contains(md5) end + def algorithm_identifier(nid) + oid = ASN1Registry.nid2oid(nid) + raise "oid for #{nid.inspect} not found!" unless oid + AlgorithmIdentifier.new(org.bouncycastle.asn1.ASN1ObjectIdentifier.new(oid)) + end + private :algorithm_identifier + def test_set_content_on_data_throws_exception p7 = PKCS7.new p7.type = ASN1Registry::NID_pkcs7_data @@ -697,12 +717,12 @@ def test_add_crl_on_signed_and_enveloped_adds_the_crl EXISTING_PKCS7_DEF = "0\202\002 \006\t*\206H\206\367\r\001\a\003\240\202\002\0210\202\002\r\002\001\0001\202\001\2700\201\331\002\001\0000B0=1\0230\021\006\n\t\222&\211\223\362,d\001\031\026\003org1\0310\027\006\n\t\222&\211\223\362,d\001\031\026\truby-lang1\v0\t\006\003U\004\003\f\002CA\002\001\0020\r\006\t*\206H\206\367\r\001\001\001\005\000\004\201\200\213kF\330\030\362\237\363$\311\351\207\271+_\310sr\344\233N\200\233)\272\226\343\003\224OOf\372 \r\301{\206\367\241\270\006\240\254\3179F\232\231Q\232\225\347\373\233\032\375\360\035o\371\275p\306\v5Z)\263\037\302|\307\300\327\a\375\023G'Ax\313\346\261\254\227K\026\364\242\337\367\362rk\276\023\217m\326\343F\366I1\263\nLuNf\234\203\261\300\030\232Q\277\231\f0\030\001\332\021\0030\201\331\002\001\0000B0=1\0230\021\006\n\t\222&\211\223\362,d\001\031\026\003org1\0310\027\006\n\t\222&\211\223\362,d\001\031\026\truby-lang1\v0\t\006\003U\004\003\f\002CA\002\001\0030\r\006\t*\206H\206\367\r\001\001\001\005\000\004\201\200\215\223\3428\2440]\0278\016\230,\315\023Tg\325`\376~\353\304\020\243N{\326H\003\005\361q\224OI\310\2324-\341?\355&r\215\233\361\245jF\255R\271\203D\304v\325\265\243\321$\bSh\031i\eS\240\227\362\221\364\232\035\202\f?x\031\223D\004ZHD\355'g\243\037\236mJ\323\210\347\274m\324-\351\332\353#A\273\002\"h\aM\202\347\236\265\aI$@\240bt=<\212\2370L\006\t*\206H\206\367\r\001\a\0010\035\006\t`\206H\001e\003\004\001\002\004\020L?\325\372\\\360\366\372\237|W\333nnI\255\200 \253\234\252\263\006\335\037\320\350{s\352r\337\304\305\216\223k\003\376f\027_\201\035#*\002yM\334" - EXISTING_PKCS7_1 = PKCS7::from_asn1(ASN1InputStream.new(EXISTING_PKCS7_DEF.to_java_bytes).read_object) + EXISTING_PKCS7_1 = PKCS7::from_asn1(org.bouncycastle.asn1.ASN1InputStream.new(EXISTING_PKCS7_DEF.to_java_bytes).read_object) def test_encrypt_integration_test certs = [X509Cert] - c = Cipher.get_instance("AES", BCP.new) - cipher = CipherSpec.new(c, "AES-128-CBC", 128) + c = Cipher.get_instance("AES", org.bouncycastle.jce.provider.BouncyCastleProvider.new) + cipher = org.jruby.ext.openssl.impl.CipherSpec.new(c, "AES-128-CBC", 128) data = "aaaaa\nbbbbb\nccccc\n".to_java_bytes PKCS7::encrypt(certs, data, cipher, PKCS7::BINARY) end @@ -731,7 +751,7 @@ def test_encrypt_integration_test PKCS7_PEM_SECOND_KEY = "=\240W\320\2437K\355\243r\264+\235\313\265\216\273\346\324\365\027\225K\314|R\006\332\200_>C!\212\240\201\276C\234\267U^\302\315a\306\361\255\262\254\243\001I\364\331\214u\372\004\t\2053x%\226G6\251\037\213D\3607}\247\227\002\361rR\234\255\261}n\373L\211Q\361Wr\353K(\e\227\034I\260\027\374\255\352\025\343$\311E\255\360\367\306\304\2408cS\b\337\313\234S\232]\234\002~" def test_pem_read_pkcs7_bio - bio = BIO::mem_buf(EXISTING_PKCS7_PEM.to_java_bytes) + bio = org.jruby.ext.openssl.impl.BIO::mem_buf(EXISTING_PKCS7_PEM.to_java_bytes) p7 = PKCS7.read_pem(bio) assert_equal ASN1Registry::NID_pkcs7_enveloped, p7.type @@ -781,7 +801,7 @@ def assert_raise_pkcs7_exception begin yield fail 'expected PKCS7Exception to be raised but did not' - rescue PKCS7Exception => e + rescue org.jruby.ext.openssl.impl.PKCS7Exception => e assert e end end @@ -877,5 +897,98 @@ def test_enveloped end end + + # NOTE: based on MRI's test_pkcs7.rb + class TestOpenSSL < TestCase + def setup + @rsa1024 = Fixtures.pkey("rsa1024") + @rsa2048 = Fixtures.pkey("rsa2048") + ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + ee1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE1") + ee2 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=EE2") + + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ["authorityKeyIdentifier","keyid:always",false], + ] + @ca_cert = issue_cert(ca, @rsa2048, 1, ca_exts, nil, nil) + ee_exts = [ + ["keyUsage","Non Repudiation, Digital Signature, Key Encipherment",true], + ["authorityKeyIdentifier","keyid:always",false], + ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], + ] + @ee1_cert = issue_cert(ee1, @rsa1024, 2, ee_exts, @ca_cert, @rsa2048) + @ee2_cert = issue_cert(ee2, @rsa1024, 3, ee_exts, @ca_cert, @rsa2048) + end + + def test_signed + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ca_certs = [@ca_cert] + + data = "aaaaa\r\nbbbbb\r\nccccc\r\n" + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + + # Normally OpenSSL tries to translate the supplied content into canonical + # MIME format (e.g. a newline character is converted into CR+LF). + # If the content is a binary, PKCS7::BINARY flag should be used. + + data = "aaaaa\nbbbbb\nccccc\n" + flag = OpenSSL::PKCS7::BINARY + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) + assert_equal OpenSSL::PKCS7, tmp.class + + p7 = OpenSSL::PKCS7.new(tmp.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + + # A signed-data which have multiple signatures can be created + # through the following steps. + # 1. create two signed-data + # 2. copy signerInfo and certificate from one to another + + tmp1 = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, [], flag) + tmp2 = OpenSSL::PKCS7.sign(@ee2_cert, @rsa1024, data, [], flag) + tmp1.add_signer(tmp2.signers[0]) + tmp1.add_certificate(@ee2_cert) + + p7 = OpenSSL::PKCS7.new(tmp1.to_der) + certs = p7.certificates + signers = p7.signers + assert(p7.verify([], store)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(2, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + assert_equal(@ee2_cert.serial, signers[1].serial) + assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s) + + assert signers[0].signed_time + assert_equal Time, signers[1].signed_time.class + end + + end end diff --git a/src/test/ruby/pkcs7/test_smime.rb b/src/test/ruby/pkcs7/test_smime.rb index 517de05e..1b6e13f4 100644 --- a/src/test/ruby/pkcs7/test_smime.rb +++ b/src/test/ruby/pkcs7/test_smime.rb @@ -1,7 +1,18 @@ -require File.expand_path('../pkcs7_helper', File.dirname(__FILE__)) +require File.expand_path('../test_helper', File.dirname(__FILE__)) module PKCS7Test class TestSMIME < TestCase + + require 'jopenssl/load' + + java_import 'org.jruby.ext.openssl.impl.BIO' + java_import 'org.jruby.ext.openssl.impl.Mime' + java_import 'org.jruby.ext.openssl.impl.MimeHeader' + java_import 'org.jruby.ext.openssl.impl.MimeParam' + java_import 'org.jruby.ext.openssl.impl.PKCS7' + java_import 'org.jruby.ext.openssl.impl.PKCS7Exception' + java_import 'org.jruby.ext.openssl.impl.SMIME' + def test_read_pkcs7_should_raise_error_when_parsing_headers_fails bio = BIO.new mime = Mime.impl { |name, *args| name == :parseHeaders ? nil : raise } @@ -20,7 +31,7 @@ def test_read_pkcs7_should_raise_error_when_content_type_is_not_there bio = BIO.new mime = Mime.impl {} - headers = ArrayList.new + headers = java.util.ArrayList.new mime.expects(:parseHeaders).with(bio).returns(headers) mime.expects(:findHeader).with(headers, "content-type").returns(nil) @@ -75,7 +86,7 @@ def test_read_pkcs7_should_call_methods_on_mime mime = Mime.impl do |name, *args| case name - when :parseHeaders then ArrayList.new + when :parseHeaders then java.util.ArrayList.new when :findHeader then if args[1] == 'content-type' MimeHeader.new(args[1], "application/pkcs7-mime") @@ -96,7 +107,7 @@ def test_read_pkcs7_throws_correct_exception_if_wrong_content_type bio = BIO.new mime = Mime.impl do |name, *args| case name - when :parseHeaders then ArrayList.new + when :parseHeaders then java.util.ArrayList.new when :findHeader then if args[1] == 'content-type' MimeHeader.new(args[1], "foo") @@ -122,7 +133,7 @@ def test_read_pkcs7_with_multipart_should_fail_if_no_boundary_found hdr = MimeHeader.new("content-type", "multipart/signed") mime = Mime.impl do |name, *args| case name - when :parseHeaders then ArrayList.new + when :parseHeaders then java.util.ArrayList.new when :findHeader then if args[1] == 'content-type' hdr @@ -148,7 +159,7 @@ def test_read_pkcs7_with_multipart_should_fail_if_null_boundary_value bio = BIO.new mime = Mime.impl {} - headers = ArrayList.new + headers = java.util.ArrayList.new hdr = MimeHeader.new("content-type", "multipart/signed") mime.expects(:parseHeaders).with(bio).returns(headers) mime.expects(:findHeader).with(headers, "content-type").returns(hdr) @@ -170,7 +181,7 @@ def _test_read_pkcs7_happy_path_without_multipart bio = BIO.new mime = Mime.impl {} - headers = ArrayList.new + headers = java.util.ArrayList.new mime.expects(:parseHeaders).with(bio).returns(headers) mime.expects(:findHeader).with(headers, "content-type").returns(MimeHeader.new("content-type", "application/pkcs7-mime")) diff --git a/src/test/ruby/pkcs7_helper.rb b/src/test/ruby/pkcs7_helper.rb index c6e88bf5..c066bb68 100644 --- a/src/test/ruby/pkcs7_helper.rb +++ b/src/test/ruby/pkcs7_helper.rb @@ -1,44 +1,5 @@ -require File.expand_path('test_helper', File.dirname(__FILE__)) - module PKCS7Test - PKCS7 = org.jruby.ext.openssl.impl.PKCS7 unless defined?(PKCS7) - Attribute = org.jruby.ext.openssl.impl.Attribute unless defined?(Attribute) - CipherSpec = org.jruby.ext.openssl.impl.CipherSpec unless defined?(CipherSpec) - Digest = org.jruby.ext.openssl.impl.Digest unless defined?(Digest) - EncContent = org.jruby.ext.openssl.impl.EncContent unless defined?(EncContent) - Encrypt = org.jruby.ext.openssl.impl.Encrypt unless defined?(Encrypt) - Envelope = org.jruby.ext.openssl.impl.Envelope unless defined?(Envelope) - IssuerAndSerial = org.jruby.ext.openssl.impl.IssuerAndSerial unless defined?(IssuerAndSerial) - RecipInfo = org.jruby.ext.openssl.impl.RecipInfo unless defined?(RecipInfo) - SignEnvelope = org.jruby.ext.openssl.impl.SignEnvelope unless defined?(SignEnvelope) - Signed = org.jruby.ext.openssl.impl.Signed unless defined?(Signed) - SMIME = org.jruby.ext.openssl.impl.SMIME unless defined?(SMIME) - Mime = org.jruby.ext.openssl.impl.Mime unless defined?(Mime) - MimeHeader = org.jruby.ext.openssl.impl.MimeHeader unless defined?(MimeHeader) - MimeParam = org.jruby.ext.openssl.impl.MimeParam unless defined?(MimeParam) - BIO = org.jruby.ext.openssl.impl.BIO unless defined?(BIO) - PKCS7Exception = org.jruby.ext.openssl.impl.PKCS7Exception unless defined?(PKCS7Exception) - ASN1Registry = org.jruby.ext.openssl.impl.ASN1Registry unless defined?(ASN1Registry) - AlgorithmIdentifier = org.bouncycastle.asn1.x509.AlgorithmIdentifier unless defined?(AlgorithmIdentifier) - SignerInfoWithPkey = org.jruby.ext.openssl.impl.SignerInfoWithPkey unless defined?(SignerInfoWithPkey) - IssuerAndSerialNumber = org.bouncycastle.asn1.pkcs.IssuerAndSerialNumber unless defined?(IssuerAndSerialNumber) - ASN1InputStream = org.bouncycastle.asn1.ASN1InputStream unless defined?(ASN1InputStream) - X509AuxCertificate = org.jruby.ext.openssl.x509store.X509AuxCertificate unless defined?(X509AuxCertificate) - - ArrayList = java.util.ArrayList unless defined?(ArrayList) - CertificateFactory = java.security.cert.CertificateFactory unless defined?(CertificateFactory) - BCP = org.bouncycastle.jce.provider.BouncyCastleProvider unless defined?(BCP) - ByteArrayInputStream = java.io.ByteArrayInputStream unless defined?(ByteArrayInputStream) - BigInteger = java.math.BigInteger unless defined?(BigInteger) - Cipher = javax.crypto.Cipher unless defined?(Cipher) - - ASN1Integer = org.bouncycastle.asn1.ASN1Integer - DERSet = org.bouncycastle.asn1.DERSet - DEROctetString = org.bouncycastle.asn1.DEROctetString - X500Name = org.bouncycastle.asn1.x500.X500Name - - MimeEnvelopedString = File::read(File.join(File.dirname(__FILE__), 'pkcs7', 'pkcs7_mime_enveloped.message')) MimeSignedString = File::read(File.join(File.dirname(__FILE__), 'pkcs7', 'pkcs7_mime_signed.message')) MultipartSignedString = File::read(File.join(File.dirname(__FILE__), 'pkcs7', 'pkcs7_multipart_signed.message')) @@ -76,7 +37,14 @@ module PKCS7Test -----END X509 CRL----- CRL - X509Cert = X509AuxCertificate.new(CertificateFactory.getInstance("X.509",BCP.new).generateCertificate(ByteArrayInputStream.new(X509CertString.to_java_bytes))) - X509CRL = CertificateFactory.getInstance("X.509",BCP.new).generateCRL(ByteArrayInputStream.new(X509CRLString.to_java_bytes)) + @@provider = org.bouncycastle.jce.provider.BouncyCastleProvider.new + + X509Cert = org.jruby.ext.openssl.x509store.X509AuxCertificate.new( + java.security.cert.CertificateFactory.getInstance( + "X.509", @@provider + ).generateCertificate(java.io.ByteArrayInputStream.new(X509CertString.to_java_bytes))) + + X509CRL = java.security.cert.CertificateFactory.getInstance("X.509", @@provider). + generateCRL(java.io.ByteArrayInputStream.new(X509CRLString.to_java_bytes)) end \ No newline at end of file diff --git a/src/test/ruby/pkey-cert.pem b/src/test/ruby/pkey-cert.pem new file mode 100644 index 00000000..c6d913d3 --- /dev/null +++ b/src/test/ruby/pkey-cert.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MREwDwYDVQQDDAhqb3Bl +bnNzbDEVMBMGCgmSJomT8ixkARkWBWpydWJ5MRMwEQYKCZImiZPyLGQBGRYDb3Jn +MB4XDTI0MDQwMzEzMjgyM1oXDTI1MDQwMzEzMjgyM1owPzERMA8GA1UEAwwIam9w +ZW5zc2wxFTATBgoJkiaJk/IsZAEZFgVqcnVieTETMBEGCgmSJomT8ixkARkWA29y +ZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ4wLFlxtSIaRic+ag6y +rq75Ay/cSSQI4BrPx/Zb7hjHZ645lgyriT1vbkUkT0LJS9Cy6WZ7br0Z7hzfWc+k +FuBoizbWI5KjF5FRiPOuTucxPkNiti1e3LbGGQMlOJbrB4guWBxCUzOb2ZeTKj95 +Edna5+g5hxsDI6ECEn6IYf0gXq0xXRNaTa1u+lfelbT6LSHxYiTADJlWEKieR2ED +MUSwZNyM37ojwLQ1SlWZXIc6AbFviSV3cckDX2TRt70W53tIXFydc2zFqPF3w+gZ +DjPukkP7JnbcgMLy9EBlbhdR9jaL8R3TbVXktKSseBRrGGDeXpPH/LPUcOun2Jzp +nEVeMmQJ+Ec9TldQ/o8bCcZ6eV/sk56con7IUX6Gq/+pSSQqqjb3/2oC4kqdM6Dj +GcpmB44oLLdg6tcI8qUd9OAopQRkPOI9yyKjA6Gpwa918u8tGX7lryzyhc4OOeFv +QwoUknNPlMXU3tMyXRLhsAekyUqqglCfaY+ZqvNW5cJbxX5G64cCZuFhb7My21yz +zhZCn3tDHegCMtA+bAIfBX+Dg2Mt5J4rVJoy9ZDzWgs6ch6NW7pO2umuxFVXoHtg +VMNsFvd5gUY+Bvd662ARbZ1vRcPK0YiYTp7OqOOpqFtplgotV/BPeClH1JyBc8Bk +j7KfmpyTNo+rpd0G9XNLAtXdAgMBAAGjdzB1MAkGA1UdEwQCMAAwCwYDVR0PBAQD +AgSwMB0GA1UdDgQWBBTQ/2kOpgjQp+7aTRZBJ312BxK1szAdBgNVHREEFjAUgRJq +b3BlbnNzbEBqcnVieS5vcmcwHQYDVR0SBBYwFIESam9wZW5zc2xAanJ1Ynkub3Jn +MA0GCSqGSIb3DQEBCwUAA4ICAQB6FOYhUUtj3OQyi9oLWxHnJvHMfcAERlES2M4N +nxOLRHA0RkR6iYChUjmxhQD9ADScnLGMWP7UcdiRNRjZ/OiOPelbcgJM6+hvWnaV +5NTK2MX3WHY7aISNJfoq/OQsDJsNjKfwyOzTca1jXKhAhl46jLJ0JZpZNBi5wefG +IjXsZ5i5WXcG3Ky0EGebiFcHzWBhQ6F3qTlBSx3Rpq3mAf7Zdn9v6fuWFraYwnS6 +apma7JSgkUrFKPeFnZz3SxB+yqneFLuvtM4UzW2vsLLAKd/BGgMSzMV9I7d/xsHl +FfizR91Q/4diiC0M+VnqB40R1UfFCoiK0Xy1U28WHrU00ij7Uc38SZMey8BDrOPY +YdS2xEEtGgFJ/x6OnJmfFM0ioJscE79phhIV+AMOTYsLerHkhbJ/m+kqmoQ+HtPZ +9aOUWmz7G28SbbZXcT6/x4Jf6O/8AGPAFmJxkSg1+9XbOcMrWGQCpwmCuqsNf6L6 +adrqeC7hjBxMaHjzDFyF8FUMLU8Un9g6fzN20gZFaAQuMuxiPoQEflTGb3BRxRTD +z8Aed06Q0Mfksf1bzoRFpFXICpI0EfHCHhUmJGpMNFiNmtZkJP8BoxiIh//Mu+x6 +dYsMY/peZYWDgkcBPdV3mQ4BNqgHhdFiDC7BvqozS84pieP3IFyKmj3KVyacCAW/ +3DO2Yg== +-----END CERTIFICATE----- diff --git a/src/test/ruby/pkey-pkcs8.pem b/src/test/ruby/pkey-pkcs8.pem new file mode 100644 index 00000000..79f6c8d2 --- /dev/null +++ b/src/test/ruby/pkey-pkcs8.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCeMCxZcbUiGkYn +PmoOsq6u+QMv3EkkCOAaz8f2W+4Yx2euOZYMq4k9b25FJE9CyUvQsulme269Ge4c +31nPpBbgaIs21iOSoxeRUYjzrk7nMT5DYrYtXty2xhkDJTiW6weILlgcQlMzm9mX +kyo/eRHZ2ufoOYcbAyOhAhJ+iGH9IF6tMV0TWk2tbvpX3pW0+i0h8WIkwAyZVhCo +nkdhAzFEsGTcjN+6I8C0NUpVmVyHOgGxb4kld3HJA19k0be9Fud7SFxcnXNsxajx +d8PoGQ4z7pJD+yZ23IDC8vRAZW4XUfY2i/Ed021V5LSkrHgUaxhg3l6Tx/yz1HDr +p9ic6ZxFXjJkCfhHPU5XUP6PGwnGenlf7JOenKJ+yFF+hqv/qUkkKqo29/9qAuJK +nTOg4xnKZgeOKCy3YOrXCPKlHfTgKKUEZDziPcsiowOhqcGvdfLvLRl+5a8s8oXO +Djnhb0MKFJJzT5TF1N7TMl0S4bAHpMlKqoJQn2mPmarzVuXCW8V+RuuHAmbhYW+z +Mttcs84WQp97Qx3oAjLQPmwCHwV/g4NjLeSeK1SaMvWQ81oLOnIejVu6TtrprsRV +V6B7YFTDbBb3eYFGPgb3eutgEW2db0XDytGImE6ezqjjqahbaZYKLVfwT3gpR9Sc +gXPAZI+yn5qckzaPq6XdBvVzSwLV3QIDAQABAoICABDMXdFLxWfvMVb2hk20RN+Y +2vFIq82zUZUXTo1HWuPvvP9f5yLGTocjH6sg/XfCp5XsgeObpPlY2RPg1gk6TCGd +iHcUy2dFgXmxlmEGOCPN82Y1g9ISmk6gf8R5Peas47pe7YVmt64p1TQG8TwXT8F6 +QRlEjqfcL/rnnO4p7nnyZ4ttzAwDkW8i81vEa9JxpLxPhVQQvuXDoXY+hKb1L1Sz +hKp1HUcHh+27IP8xD4Xer5JXnVFIu2EUxhsPODtQxNkvKHu/TqQU42ltBfVOP9dc +9NRaRc3mgZfyM+TowIWicwp81uiLbLdr15ELLZ9Bdi/HPXTlDl1MaIdl5hdXqXv7 +lKgW290mWNlwADyZLBvvuBMv+s/ERN1/kY+VG/NLT0Kqo1Z8FSWVTxJ6HP/J7dWb +H7r3J5nASIMUkPuvO7jv81OxcnDAx6wYZGl3bQMz3gGDs7mq8LeiTclJa6AwdALV +5l6ScE5YA/PxlzcHtXZCbZw1i0++WMz/+/VMwFV6h+dGmM3Q2ByZO2hHzVi0EBke +2zjzbqRLbvFZ0SQzfMnyApw2BmvURRfChEYVP/Eb+nmH+fCWxyuzVfCR4CUX6Q2a +BZ3SilyziCIYkICWEc5u3fOXLwmKKid/P9HgU70qMgUqWSJwo345Ah+wu2bcn/n6 +QwowwYWISxSnNydQeyLzAoIBAQDKc/HHVbbGV2bvTO0PPlHFsukD/rbBOC3bBco6 +Xl0d5MPD0aMzPSR9tLHh749f3QuLExSjPAZ5hwuBiHLPagYV1i4xYRNp32+0yAV4 +TMqXEpeU1PNwI+B8WD0l/jFcuq4GNjWdquwLPTFo2RDKnRmTudoFe/5sd6ShZ/wD +ZXq/1Qbk3e6uky8D8ivXXuV6xk2okxLKFIcSKxIeVHDF/KKbqzS/JiUJjnmta+D3 +1RgHEo7F4jqPcOyCcMMc+ImWxpfu5AiF/+nmvrb3jmBL/HVW1UMKlf2JzQtTPI9q +Z8p8IKfyNVSqi5AZ7c+1Ga56tF8XmfylabQeYMLxDf63rY9rAoIBAQDIBxIRo96t +vIPfowHyKrkc38/xWtB1l3V+L8xBugFBH2gvcuH02XU7JOL5REGEy8xWl8Zrxv/i +SRg9WO968cSVyXX/behZFRqtnvzsYGaFaMw2LHlpD2NgoBcpfdvOU8jGdTynV62Z +ELQap+e39/MqUUdd9O4RAogVTeYj5bjkJrGFQLEKgA0sQSDn0D3cbvAzoOUS4iPR +XKFZu7eBt+c7I+4vFYKUFXg2FSa4oFKag2OfGt5+9V2CyF1bym5My9HhPn1INnDQ +tpAy7k9L0Z+LwyihFwuEqx4y9wT9cOHtzRnTd1R056M4qqaC2n+tPgPBft66Jbe/ +yff1EBnS5enXAoIBAQCe+SiUBGR/nsY/hsJnw+HmQjTjZDIRy5GGeSdxagPoo2ht +7cIfTBXJeAhuh/SnrCWSUatU+p+C8l5Amuq8oF6i6wpe0AG0kzVeWeqhqnsy4fCR +eB6ooiN2wLtTiCUu0ZAxZIP5iOvNb4HK+RhMYcxhygC/abxBZDmsXMDx0c/vrtG5 +CeSQxRaYHKP8fJHqNKlqhAKvGCtwQs35bwkXDuiJIIlMyCExPeWiHiILuhMiCpp+ +HMUrOdseGnm72BYChXNsrTdcMbZz8LIv9mb7hASH6PK570ml2ZLZC6b6QQIpMmNu +uB4BxZGkKq99X+s4g0ZgfBR9t2EusYCiVQD9LpZ9AoIBAQCqoe12AtZYWrIcOk/U +p0FWMk6O0yt0xIlD5b5Wazfat9IGd/ftCPwKQgMlJGbHw5pCXCK1mgJ6pGP7ITzZ +IhjSthLknHtSUsB5T2Duu6V5NNbPd8zyJGBf8vEPQgAZ2DszoJrnHdJG+orJGmZ+ +9xMGDGPW8LOM6x3vHo6uHK9GYQOaan8cN/QExmItneTHgBOD10LSgvqPBHfXtlxq +Wdj1CEeM/spvTIlJiW2InasZh++ZsDARGXp1sDqDrQG+nTgi702sJlTClpoX+FbI +lL+HWCn2kVQQDnEqhLJLCNqSIx5e2DLBrz/7fQeiJIY54OcPTwzsunQvNTlCYkPF +Nr/tAoIBAEpmGHKpx2Yp/nTXICyI8V8j12TWJqxZLkQjvpFAdA6diBy/CCFK9pa9 +jKEoC6Z9R4oAWaqzCT4tQjXOmHPYB8qaxKXDYN60X7ugzTM8zUspI/tP1HGTyAUV +uIHQ6DB0tZq8J4cZ4s3BlWGk+9rEWcan6/lY3/c34ZLvUyAtJ/BOpoDmNPY/G4/l +IT2R2Mn4789c1+wKrpe/5ffRfX0o2symYt8KjeO4PnvNN4YBKH3zpJ+OtzEKFFgm +/ddfDIqVbsMpOV0vb03z3N5cKpqlXhLMJUIjscTMWk5DMsmhLK68yhTDTaXZzK/Y +bXMAaZvmPMWY39farAQNCHnnSQLhCmE= +-----END PRIVATE KEY----- diff --git a/src/test/ruby/rsa/test_rsa.rb b/src/test/ruby/rsa/test_rsa.rb index 6f8c0391..d9b24d1c 100644 --- a/src/test/ruby/rsa/test_rsa.rb +++ b/src/test/ruby/rsa/test_rsa.rb @@ -5,10 +5,91 @@ class TestRSA < TestCase def setup super - self.class.disable_security_restrictions! + require 'base64' end + def test_private + # Generated by key size and public exponent + key = OpenSSL::PKey::RSA.new(512, 3) + assert(key.private?) + + # Generated by DER + key2 = OpenSSL::PKey::RSA.new(key.to_der) + assert(key2.private?) + + # public key + key3 = key.public_key + assert(!key3.private?) + + # Generated by public key DER + key4 = OpenSSL::PKey::RSA.new(key3.to_der) + assert(!key4.private?) + rsa1024 = Fixtures.pkey("rsa1024") + + #if !openssl?(3, 0, 0) + # Generated by RSA#set_key + key5 = OpenSSL::PKey::RSA.new + key5.set_key(rsa1024.n, rsa1024.e, rsa1024.d) + assert(key5.private?) + + # Generated by RSA#set_key, without d + key6 = OpenSSL::PKey::RSA.new + key6.set_key(rsa1024.n, rsa1024.e, nil) + assert(!key6.private?) + #end + end + + def test_dup + key = Fixtures.pkey("rsa1024") + 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_new + key = OpenSSL::PKey::RSA.new(512) + assert_equal 512, key.n.num_bits + assert_equal 65537, key.e + assert_not_nil key.d + + # Specify public exponent + key2 = OpenSSL::PKey::RSA.new(512, 3) + assert_equal 512, key2.n.num_bits + assert_equal 3, key2.e + assert_not_nil key2.d + end + + def test_s_generate + key1 = OpenSSL::PKey::RSA.generate(512) + assert_equal 512, key1.n.num_bits + assert_equal 65537, key1.e + + # Specify public exponent + key2 = OpenSSL::PKey::RSA.generate(512, 3) + assert_equal 512, key2.n.num_bits + assert_equal 3, key2.e + assert_not_nil key2.d + end + + # TODO: not implemented properly + # def test_new_break + # assert_nil(OpenSSL::PKey::RSA.new(1024) { break }) + # assert_raise(RuntimeError) do + # OpenSSL::PKey::RSA.new(1024) { raise } + # end + # end + + def test_oid + key = OpenSSL::PKey::RSA.new + assert_equal 'rsaEncryption', key.oid + end + def test_rsa_private_decrypt key_file = File.join(File.dirname(__FILE__), 'private_key.pem') base64_cipher = "oj1VB1Lnh6j5Ahoq4dllIXkStZHaT9RvizB0x+yIUDtzi6grSh9vXoCchb+U\nkyLOcMmIXopv1Oe7h2te+XS63AG0EAfUhKTFVDYkm7VmcXue25MPr+P+0w+7\nWjZci4VRBLq3T2qZa3IJhQPsNAtEE1DYXnEjNe0jcFa2bu8TPNscoogo5aAw\nQGT+3cKe7A053czG47Sip7aIo+4NlJHE9kFMOTLaWi3fvv/M9/VKo3Bmm/88\n8Ai09LncNTpq787CRHw/wfjuPlQJOiLt+i7AZHBl6x0jK9bqkhPK5YwP0vmc\nuL52QLzgPxj9E78crg47iJDOgNwU/ux1/VuKnlQ9PQ==\n" @@ -67,9 +148,14 @@ def test_rsa_from_params_public_first rsa = OpenSSL::PKey::RSA.new rsa.e, rsa.n = key.e, key.n assert_nothing_raised { rsa.public_encrypt('Test string') } - [:e, :n].each {|param| assert_equal(key.send(param), rsa.send(param)) } + [:e, :n].each { |param| assert_equal(key.send(param), rsa.send(param)) } + + rsa = OpenSSL::PKey::RSA.new + rsa.set_key(key.n, key.e, key.d) + # rsa.d, rsa.p, rsa.q, rsa.iqmp, rsa.dmp1, rsa.dmq1 = key.d, key.p, key.q, key.iqmp, key.dmp1, key.dmq1 + rsa.set_factors(key.p, key.q) + rsa.set_crt_params(key.dmp1, key.dmq1, key.iqmp) - rsa.d, rsa.p, rsa.q, rsa.iqmp, rsa.dmp1, rsa.dmq1 = key.d, key.p, key.q, key.iqmp, key.dmp1, key.dmq1 assert_nothing_raised { rsa.private_encrypt('Test string') } [:e, :n, :d, :p, :q, :iqmp, :dmp1, :dmq1].each do |param| assert_equal(key.send(param), rsa.send(param), param) @@ -103,4 +189,242 @@ def test_read_private_key_with_password assert key.is_a?(OpenSSL::PKey::RSA) end + def test_RSAPrivateKey + rsa1024 = Fixtures.pkey("rsa1024") + assert_not_equal nil, rsa1024.dmp1 + assert_not_equal nil, rsa1024.dmq1 + assert_not_equal nil, rsa1024.iqmp + + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(0), + OpenSSL::ASN1::Integer(rsa1024.n), + OpenSSL::ASN1::Integer(rsa1024.e), + OpenSSL::ASN1::Integer(rsa1024.d), + OpenSSL::ASN1::Integer(rsa1024.p), + OpenSSL::ASN1::Integer(rsa1024.q), + OpenSSL::ASN1::Integer(rsa1024.dmp1), # nil??? + OpenSSL::ASN1::Integer(rsa1024.dmq1), + OpenSSL::ASN1::Integer(rsa1024.iqmp) + ]) + key = OpenSSL::PKey::RSA.new(asn1.to_der) + assert_predicate key, :private? + assert_same_rsa rsa1024, key + + pem = <<~EOF + -----BEGIN RSA PRIVATE KEY----- + MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx + aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ + Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB + AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 + maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T + gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 + 74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE + JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX + sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII + 8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA + wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi + qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD + dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== + -----END RSA PRIVATE KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert_same_rsa rsa1024, key + + assert_equal asn1.to_der, rsa1024.to_der + assert_equal pem, rsa1024.export + + # Unknown PEM prepended + cert = issue_cert(OpenSSL::X509::Name.new([["CN", "nobody"]]), rsa1024, 1, [], nil, nil) + str = cert.to_text + cert.to_pem + rsa1024.to_pem + key = OpenSSL::PKey::RSA.new(str) + assert_same_rsa rsa1024, key + end + + def test_RSAPrivateKey_encrypted + rsa1024 = Fixtures.pkey("rsa1024") + # key = abcdef + pem = <<-EOF +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,733F5302505B34701FC41F5C0746E4C0 + +zgJniZZQfvv8TFx3LzV6zhAQVayvQVZlAYqFq2yWbbxzF7C+IBhKQle9IhUQ9j/y +/jkvol550LS8vZ7TX5WxyDLe12cdqzEvpR6jf3NbxiNysOCxwG4ErhaZGP+krcoB +ObuL0nvls/+3myy5reKEyy22+0GvTDjaChfr+FwJjXMG+IBCLscYdgZC1LQL6oAn +9xY5DH3W7BW4wR5ttxvtN32TkfVQh8xi3jrLrduUh+hV8DTiAiLIhv0Vykwhep2p +WZA+7qbrYaYM8GLLgLrb6LfBoxeNxAEKiTpl1quFkm+Hk1dKq0EhVnxHf92x0zVF +jRGZxAMNcrlCoE4f5XK45epVZSZvihdo1k73GPbp84aZ5P/xlO4OwZ3i4uCQXynl +jE9c+I+4rRWKyPz9gkkqo0+teJL8ifeKt/3ab6FcdA0aArynqmsKJMktxmNu83We +YVGEHZPeOlyOQqPvZqWsLnXQUfg54OkbuV4/4mWSIzxFXdFy/AekSeJugpswMXqn +oNck4qySNyfnlyelppXyWWwDfVus9CVAGZmJQaJExHMT/rQFRVchlmY0Ddr5O264 +gcjv90o1NBOc2fNcqjivuoX7ROqys4K/YdNQ1HhQ7usJghADNOtuLI8ZqMh9akXD +Eqp6Ne97wq1NiJj0nt3SJlzTnOyTjzrTe0Y+atPkVKp7SsjkATMI9JdhXwGhWd7a +qFVl0owZiDasgEhyG2K5L6r+yaJLYkPVXZYC/wtWC3NEchnDWZGQcXzB4xROCQkD +OlWNYDkPiZioeFkA3/fTMvG4moB2Pp9Q4GU5fJ6k43Ccu1up8dX/LumZb4ecg5/x +-----END RSA PRIVATE KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem, "abcdef") + assert_same_rsa rsa1024, key + key = OpenSSL::PKey::RSA.new(pem) { "abcdef" } + assert_same_rsa rsa1024, key + assert_predicate key, :private? + + ## + der = "0\x82\x02^\x02\x01\x00\x02\x81\x81\x00\xCB\xC2\xC4\xB0\xD4@\xA7>\xD4\xFE>C\xA0\x1E\x17\x06\x03\xBDg\xC0-\xBF\x9C\xBF9T\x11\xA7F\xA0\xF1:\xA8\xD5\x87\xB0\xB1h\xA3\xC4E\x81\xEC\x93\x80O\nA7n\xBBS\x84\xF5\x9C\xF6H\xC7\x11\x04;\xB9\xFFX\xD6\xB6\xC2\xCFIZ\xC8\xDA\x87\xCB,\x10\x11R\xC5\x9A\x9D\\\xA4\x8B\x7FCx\x1E.\xFF\x19\x0F\xDAb\x86\x8C\n$<\x8C\x0E#z\x02\xB6\x14\x99\x973\xBDn=\xEF\xA3\x14\xDF\xE9y\xE0N\xA5\x17\xF2_\x14E9\x87\x02\x03\x01\x00\x01\x02\x81\x81\x00\xA4\xA5\xFC\xC4\x1A\xAF'\xB5\xC8\xEC\xAC\xA9~\x8F\xF1\xF0\xC5B\xDE\xBCV\xFBW\x90\xD9\xA5J\"\x8E\x1A\xEC\xF2\x86\x8D\f\xF9ER\x15\xCB:\x93R\xF4\x99\xA0\xC4\xDB\x90\feH\x86\xCE\xE5\x87\xC9\xA59\xE5\xDE\xD1\\8\x1E\xB2@\xCCj@\xF5\xBF\xA6^\x8D\x8Bq&D\x97lD\xCEL\xB0\xF0Qd\xE7\xEB_\x13\x82v\xF5\xEE\xD55\x8EKT\xB5N\xBB\xC4\xAF\xF4\xF2/\xD7\xA1\xD9`\x9D\bl\xE9Z\aN\xE0\xA1\xDC{\xBB\xEC\xEE\x91\x02A\x00\xEF\xE81\xA8\e\x99G\x9E\xF6\xEF\x8F\xDA\x92%\xBFJ\xE5\xE6\xD1%\xCF\x12\xF8\xB2;S.I\x1C-#\xCE\xC1\xD3`\xF4\xAA\x05|\x0FU\aFn\x00\x84\xC9\xC9\xF0\xC0\xAC\xD7\xCD=\x90\xC4\x04&$\x14\n\xBA}\x99\x02A\x00\xD9m\xDAx\x01;^.\xB3\xA3g\x86\xE0,xP\xD3\a0\x87_\x05\x14\xBBV\n\x1E\x93\f\x9B\x1Ag\x1A\xA6\xA5th\x17\xB0\xE6A\xDEX\t\xB1?U\x94g\xF7`f\x1D\x90\xB4\xCDU\xBA @\xA9J\xA4\x1F\x02A\x00\xE2K\t\e\xE2\xFC\x90|7\xBC\xFC\xDAT\xE4\xDA-\xD1\xF2\b\xF2;\x03P&\xFE\xA2\x95\x94L\xC9\x9Fo\x15\x91GqA6\xEF0\x9F)\xBF\x9B_M\xE1\xF1c\xF0\xBA\x98\xCCu\xF8)\x8D(T\xFB\xBA\xAA\xF2q\x02A\x00\xC1\xAEl\x9C\xD7\xA4\x15\xCA\x8E4\xB2\x04\xE0n\\\xA2\xCA\xC8\xAD\xBE\xF8\xB2\xA2\xFC\x19\xB1\x9B\xF8\xAB\x93\x02\x9A\xF3\x8F\x9C\xF5+\xC0f\xD1E\xBD\x958\xD5\x0E0\xE2\xA9\x16 e\xF6R/\xECu?\xFEx\xFB\x14\xC5K\x02@cQq\x03\xF3w@\xF0\x97\n3\xE1\xE5\x8A\x85\xC4\x03\x10\xB3eUa7\xE0\xFA\xDD\f\x11\xC2Ct\xF3\x10\x0F\x92W\xEA\x0EG*q\xC5\x83i\x99\xB6\x85\xD0\xADi\x89J~\xE9\xF0bJ\xF3#S\xA2\x91\x04\xAC" + pp OpenSSL::ASN1.decode(key.to_der) if $DEBUG + assert_equal der, key.to_der + + cipher = OpenSSL::Cipher.new("aes-128-cbc") + exported = rsa1024.to_pem(cipher, "abcdef\0\1") + assert_same_rsa rsa1024, OpenSSL::PKey::RSA.new(exported, "abcdef\0\1") + assert_raise(OpenSSL::PKey::RSAError) { + OpenSSL::PKey::RSA.new(exported, "abcdef") + } + end + + def test_RSAPublicKey_custom + rsa1024 = Fixtures.pkey("rsa1024") + + asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(rsa1024.n), OpenSSL::ASN1::Integer(rsa1024.e) ]) + + key = OpenSSL::PKey::RSA.new(asn1.to_der) + assert_not_predicate key, :private? + n = 143085709396403084580358323862163416700436550432664688288860593156058579474547937626086626045206357324274536445865308750491138538454154232826011964045825759324933943290377903384882276841880081931690695505836279972214003660451338124170055999155993192881685495391496854691199517389593073052473319331505702779271 + assert_equal n, key.n + assert_same_rsa dup_public(rsa1024), key + + ## + der = "0\x81\x9F0\r\x06\t*\x86H\x86\xF7\r\x01\x01\x01\x05\x00\x03\x81\x8D\x000\x81\x89\x02\x81\x81\x00\xCB\xC2\xC4\xB0\xD4@\xA7>\xD4\xFE>C\xA0\x1E\x17\x06\x03\xBDg\xC0-\xBF\x9C\xBF9T\x11\xA7F\xA0\xF1:\xA8\xD5\x87\xB0\xB1h\xA3\xC4E\x81\xEC\x93\x80O\nA7n\xBBS\x84\xF5\x9C\xF6H\xC7\x11\x04;\xB9\xFFX\xD6\xB6\xC2\xCFIZ\xC8\xDA\x87\xCB,\x10\x11R\xC5\x9A\x9D\\\xA4\x8B\x7FCx\x1E.\xFF\x19\x0F\xDAb\x86\x8C\n$<\x8C\x0E#z\x02\xB6\x14\x99\x973\xBDn=\xEF\xA3\x14\xDF\xE9y\xE0N\xA5\x17\xF2_\x14E9\x87\x02\x03\x01\x00\x01" + pp OpenSSL::ASN1.decode(key.to_der) if $DEBUG + assert_equal der, key.to_der + + pem = <<-EOF +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF +geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u +/xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE= +-----END RSA PUBLIC KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert_not_predicate key, :private? + assert_same_rsa dup_public(rsa1024), key + + ## + assert_equal der, key.to_der + + expected = "b48c0b2bbd35b906c5af4e46ed7355e4aaeadc99" + assert_equal expected, OpenSSL::Digest::SHA1.hexdigest(key.to_der) + end if !defined?(JRUBY_VERSION) || JRUBY_VERSION > '9.1' # set_key only since Ruby 2.3 + + def test_RSAPublicKey + rsa1024 = Fixtures.pkey("rsa1024") + rsa1024pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(rsa1024.n), + OpenSSL::ASN1::Integer(rsa1024.e) + ]) + key = OpenSSL::PKey::RSA.new(asn1.to_der) + assert_not_predicate key, :private? + assert_same_rsa rsa1024pub, key + + pem = <<~EOF + -----BEGIN RSA PUBLIC KEY----- + MIGJAoGBAMvCxLDUQKc+1P4+Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RF + geyTgE8KQTduu1OE9Zz2SMcRBDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u + /xkP2mKGjAokPIwOI3oCthSZlzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAE= + -----END RSA PUBLIC KEY----- + EOF + key = OpenSSL::PKey::RSA.new(pem) + assert_same_rsa rsa1024pub, key + end + + def test_export + rsa1024 = Fixtures.pkey("rsa1024") + + #pub = OpenSSL::PKey.read(rsa1024.public_to_der) # TODO not supported + pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + assert_not_equal rsa1024.export, pub.export + assert_equal rsa1024.public_to_pem, pub.export + + # PKey is immutable in OpenSSL >= 3.0 + #if !openssl?(3, 0, 0) + key = OpenSSL::PKey::RSA.new + + # key has only n, e and d + key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) + assert_equal rsa1024.public_key.export, key.export + + # key has only n, e, d, p and q + key.set_factors(rsa1024.p, rsa1024.q) + assert_equal rsa1024.public_key.export, key.export + + # key has n, e, d, p, q, dmp1, dmq1 and iqmp + key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) + #assert_equal rsa1024.export, key.export # TODO does not pass + #end + end + + def test_to_der + rsa1024 = Fixtures.pkey("rsa1024") + + pub = OpenSSL::PKey::RSA.new(rsa1024.public_to_der) + assert_not_equal rsa1024.to_der, pub.to_der + assert_equal rsa1024.public_to_der, pub.to_der + + # PKey is immutable in OpenSSL >= 3.0 + #if !openssl?(3, 0, 0) + key = OpenSSL::PKey::RSA.new + + # key has only n, e and d + key.set_key(rsa1024.n, rsa1024.e, rsa1024.d) + assert_equal rsa1024.public_key.to_der, key.to_der + + # key has only n, e, d, p and q + key.set_factors(rsa1024.p, rsa1024.q) + assert_equal rsa1024.public_key.to_der, key.to_der + + # key has n, e, d, p, q, dmp1, dmq1 and iqmp + key.set_crt_params(rsa1024.dmp1, rsa1024.dmq1, rsa1024.iqmp) + #assert_equal rsa1024.to_der, key.to_der # TODO does not pass + #end + end + + def test_to_java + key_file = File.join(File.dirname(__FILE__), 'private_key.pem') + pkey = OpenSSL::PKey::RSA.new(File.read(key_file)) + assert_kind_of java.security.PublicKey, pkey.to_java + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.PublicKey) + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.interfaces.RSAPublicKey) + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.Key) + assert_kind_of java.security.PrivateKey, pkey.to_java(java.security.PrivateKey) + assert_kind_of java.security.PrivateKey, pkey.to_java(java.security.interfaces.RSAPrivateKey) + priv_key = pkey.to_java(java.security.PrivateKey) + if priv_key.is_a? org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey + assert_kind_of java.security.PrivateKey, pkey.to_java(org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey) + end + assert_raise_kind_of(TypeError) { pkey.to_java(java.security.interfaces.ECPrivateKey) } + end if defined?(JRUBY_VERSION) + + private + + def assert_same_rsa(expected, key) + check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp]) + end + + def check_component(base, test, keys) + keys.each { |comp| assert_equal base.send(comp), test.send(comp) } + end + + def dup_public(key) + case key + when OpenSSL::PKey::RSA + rsa = OpenSSL::PKey::RSA.new + rsa.set_key(key.n, key.e, nil) + rsa + else + raise "unknown key type: #{key.class}" + end + end + end diff --git a/src/test/ruby/ssl/letsencrypt/isrg-root-x1-cross-signed.pem b/src/test/ruby/ssl/letsencrypt/isrg-root-x1-cross-signed.pem new file mode 100644 index 00000000..a788a405 --- /dev/null +++ b/src/test/ruby/ssl/letsencrypt/isrg-root-x1-cross-signed.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC +ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL +wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D +LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK +4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 +bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y +sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ +Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 +FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc +SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql +PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND +TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 +c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx ++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB +ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu +b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E +U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu +MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC +5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW +9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG +WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O +he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC +Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 +-----END CERTIFICATE----- diff --git a/src/test/ruby/ssl/letsencrypt/isrgrootx1.pem b/src/test/ruby/ssl/letsencrypt/isrgrootx1.pem new file mode 100644 index 00000000..b85c8037 --- /dev/null +++ b/src/test/ruby/ssl/letsencrypt/isrgrootx1.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3-cross-signed.pem b/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3-cross-signed.pem new file mode 100644 index 00000000..1d82449a --- /dev/null +++ b/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3-cross-signed.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEZTCCA02gAwIBAgIQQAF1BIMUpMghjISpDBbN3zANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTIwMTAwNzE5MjE0MFoXDTIxMDkyOTE5MjE0MFow +MjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxCzAJBgNVBAMT +AlIzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuwIVKMz2oJTTDxLs +jVWSw/iC8ZmmekKIp10mqrUrucVMsa+Oa/l1yKPXD0eUFFU1V4yeqKI5GfWCPEKp +Tm71O8Mu243AsFzzWTjn7c9p8FoLG77AlCQlh/o3cbMT5xys4Zvv2+Q7RVJFlqnB +U840yFLuta7tj95gcOKlVKu2bQ6XpUA0ayvTvGbrZjR8+muLj1cpmfgwF126cm/7 +gcWt0oZYPRfH5wm78Sv3htzB2nFd1EbjzK0lwYi8YGd1ZrPxGPeiXOZT/zqItkel +/xMY6pgJdz+dU/nPAeX1pnAXFK9jpP+Zs5Od3FOnBv5IhR2haa4ldbsTzFID9e1R +oYvbFQIDAQABo4IBaDCCAWQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8E +BAMCAYYwSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5p +ZGVudHJ1c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTE +p7Gkeyxx+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEE +AYLfEwEBATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0 +LmNvbS9EU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYf +r52LFMLGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0B +AQsFAAOCAQEA2UzgyfWEiDcx27sT4rP8i2tiEmxYt0l+PAK3qB8oYevO4C5z70kH +ejWEHx2taPDY/laBL21/WKZuNTYQHHPD5b1tXgHXbnL7KqC401dk5VvCadTQsvd8 +S8MXjohyc9z9/G2948kLjmE6Flh9dDYrVYA9x2O+hEPGOaEOa1eePynBgPayvUfL +qjBstzLhWVQLGAkXXmNs+5ZnPBxzDJOLxhF2JIbeQAcH5H0tZrUlo5ZYyOqA7s9p +O5b85o3AM/OJ+CktFBQtfvBhcJVd9wvlwPsk+uyOy2HI7mNxKKgsBTt375teA2Tw +UdHkhVNcsAKX1H7GNNLOEADksd86wuoXvg== +-----END CERTIFICATE----- diff --git a/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3.pem b/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3.pem new file mode 100644 index 00000000..43b222a6 --- /dev/null +++ b/src/test/ruby/ssl/letsencrypt/lets-encrypt-r3.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/src/test/ruby/ssl/test_context.rb b/src/test/ruby/ssl/test_context.rb index 75d864f2..380e505a 100644 --- a/src/test/ruby/ssl/test_context.rb +++ b/src/test/ruby/ssl/test_context.rb @@ -10,9 +10,6 @@ def test_methods assert methods.include?(:'TLSv1_1') assert ! methods.include?(:'TLSv1.1') - assert ! methods.include?(:SSLv2) - assert ! methods.include?(:SSLv2_client) - assert methods.include?(:'TLSv1_1_client') assert methods.include?(:'TLSv1_1_server') @@ -105,123 +102,144 @@ def test_context_set_ssl_version assert_raises(TypeError) { context.ssl_version = 12 } end - def test_context_ciphers - self.class.disable_security_restrictions + def test_context_minmax_version + context = OpenSSL::SSL::SSLContext.new + context.min_version = OpenSSL::SSL::TLS1_VERSION + context.max_version = OpenSSL::SSL::TLS1_2_VERSION + context.max_version = OpenSSL::SSL::TLS1_3_VERSION + end if RUBY_VERSION > '2.3' + def test_context_ciphers context = OpenSSL::SSL::SSLContext.new context.ciphers = "ALL" all_ciphers = context.ciphers.map { |cipher_array| cipher_array[0] } - # NOTE: assuming JCE installed ()CryptoSecurity.setAllPermissionPolicy) - # ... otherwise on Java 8 (1.8.0_112-b15) : - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_DH_anon_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DH_anon_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_anon_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_DH_anon_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 - # Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA - # Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - - jce_installed = true # || nil - expected_ciphers = [jce_installed && "ECDHE-ECDSA-AES256-SHA", - jce_installed && "ECDHE-RSA-AES256-SHA", - jce_installed && "AES256-SHA", - jce_installed && "ECDH-ECDSA-AES256-SHA", - jce_installed && "ECDH-RSA-AES256-SHA", - jce_installed && "DHE-RSA-AES256-SHA", - jce_installed && "DHE-DSS-AES256-SHA", - "ECDHE-ECDSA-AES128-SHA256", - "ECDHE-RSA-AES128-SHA256", - "ECDH-ECDSA-AES128-SHA256", - "ECDH-RSA-AES128-SHA256", - "ECDHE-ECDSA-AES128-SHA", - "ECDHE-RSA-AES128-SHA", - "AES128-SHA", - "ECDH-ECDSA-AES128-SHA", - "ECDH-RSA-AES128-SHA", - "DHE-RSA-AES128-SHA", - "DHE-DSS-AES128-SHA", - "ECDHE-ECDSA-DES-CBC3-SHA", - "ECDHE-RSA-DES-CBC3-SHA", - "DES-CBC3-SHA", - "ECDH-ECDSA-DES-CBC3-SHA", - "ECDH-RSA-DES-CBC3-SHA", - "EDH-RSA-DES-CBC3-SHA", - "EDH-DSS-DES-CBC3-SHA", - jce_installed && "AECDH-AES256-SHA", - jce_installed && "ADH-AES256-SHA", - "AECDH-AES128-SHA", - "ADH-AES128-SHA", - "AECDH-DES-CBC3-SHA", - "ADH-DES-CBC3-SHA"] - - #expected_ciphers.compact.each do |cipher| - # assert all_ciphers.include?(cipher), "#{cipher} should have been included" - #end + jce_installed = true # always assume installed (Java 8+) + + defunct_ciphers = [ # in terms of OpenSSL not reporting them on "ALL" (Ubuntu 16 LTS) + jce_installed && "AECDH-AES256-SHA" && nil, # dropped in Java 11 + jce_installed && "ADH-AES256-SHA" && nil, # dropped in Java 11 + #"AECDH-DES-CBC3-SHA", + #"ADH-DES-CBC3-SHA", + ] + + shared_ciphers = [ + jce_installed && "ECDHE-ECDSA-AES256-SHA", + jce_installed && "ECDHE-RSA-AES256-SHA", + jce_installed && "AES256-SHA", + jce_installed && "DHE-RSA-AES256-SHA", + jce_installed && "DHE-DSS-AES256-SHA", + "ECDHE-ECDSA-AES128-SHA", + "ECDHE-RSA-AES128-SHA", + "AES128-SHA", + "DHE-RSA-AES128-SHA", + "DHE-DSS-AES128-SHA", + "AECDH-AES128-SHA" && nil, # dropped in Java 11 + "ADH-AES128-SHA" && nil, # dropped + + "ECDHE-RSA-AES128-SHA256", "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES256-SHA384", "ECDHE-RSA-AES256-GCM-SHA384", + + # added support in 0.10.3 + "ECDHE-ECDSA-AES256-SHA384", + "ECDHE-RSA-AES256-SHA384", + "DHE-RSA-AES256-SHA256", + "DHE-DSS-AES256-SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES128-GCM-SHA256", "ECDHE-RSA-AES256-GCM-SHA384", + "DHE-DSS-AES128-GCM-SHA256", "DHE-DSS-AES256-GCM-SHA384", + "DHE-RSA-AES128-GCM-SHA256", "DHE-RSA-AES256-GCM-SHA384", + "AES128-GCM-SHA256", "AES256-GCM-SHA384", + + # TLS 1.3 + 'TLS_AES_256_GCM_SHA384' + ] + + expected_ciphers = [ + #"ECDH-ECDSA-AES128-SHA256", + #"ECDH-RSA-AES128-SHA256", + #"ECDH-ECDSA-AES128-SHA", + #"ECDH-RSA-AES128-SHA", + ] + defunct_ciphers + shared_ciphers + + expected_ciphers.compact.each do |cipher| + assert all_ciphers.include?(cipher), "#{cipher} should have been included" + end diff = (expected_ciphers - all_ciphers).compact assert_equal [], diff end + def test_set_ciphers_by_group_name + context = OpenSSL::SSL::SSLContext.new + context.ciphers = "AES" + + actual = context.ciphers.map { |cipher| cipher[0] } + assert actual.include?("ECDHE-RSA-AES128-SHA") + assert actual.include?("ECDHE-ECDSA-AES128-SHA") + assert actual.include?("AES128-SHA") + end + + def test_set_ciphers_by_cipher_name + context = OpenSSL::SSL::SSLContext.new + context.ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384" + actual = context.ciphers.map { |cipher| cipher[0] } + assert actual.include?("ECDHE-ECDSA-AES128-GCM-SHA256") + assert actual.include?("ECDHE-ECDSA-AES256-GCM-SHA384") + end + + def test_set_ciphers_by_array_of_names + context = OpenSSL::SSL::SSLContext.new + context.ciphers = ["ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384"] + actual = context.ciphers.map { |cipher| cipher[0] } + assert actual.include?("ECDHE-ECDSA-AES128-GCM-SHA256") + assert actual.include?("ECDHE-ECDSA-AES256-GCM-SHA384") + end + + def test_set_ciphers_by_array_of_name_version_bits + context = OpenSSL::SSL::SSLContext.new + context.ciphers = [["ECDHE-ECDSA-AES128-GCM-SHA256", "TLSv1.2", 128, 128]] + actual = context.ciphers.map { |cipher| cipher[0] } + assert actual.include?("ECDHE-ECDSA-AES128-GCM-SHA256") + end + + def test_set_ciphers_by_array_supports_setting_java_names + context = OpenSSL::SSL::SSLContext.new + context.ciphers = [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", # Java name + "ECDHE-ECDSA-AES256-GCM-SHA384", # Ruby name + 'TLS_AES_256_GCM_SHA384' # same name in Ruby/Java + ] + actual = context.ciphers.map { |cipher| cipher[0] } + assert actual.include?("ECDHE-ECDSA-AES128-GCM-SHA256"), actual.inspect + assert actual.include?("ECDHE-ECDSA-AES256-GCM-SHA384"), actual.inspect + assert actual.include?("TLS_AES_256_GCM_SHA384"), actual.inspect + + context.ciphers = [ 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' ] + actual = context.ciphers.map { |cipher| cipher[0] } + assert_equal actual, ['ECDHE-RSA-AES256-GCM-SHA384'] + end + + def test_set_ciphers_empty_array + context = OpenSSL::SSL::SSLContext.new + ex = assert_raise(OpenSSL::SSL::SSLError) do + context.ciphers = [] + end + # MRI: SSL_CTX_set_cipher_list: no cipher match + assert_include ex.message, "no cipher match" + end + + def test_invalid_ciphers_does_not_mutate_context + context = OpenSSL::SSL::SSLContext.new + ciphers = context.ciphers + assert !ciphers.empty? + begin + context.ciphers = ['AES256-SHA123'] + fail 'raise expected' + rescue OpenSSL::SSL::SSLError + end + assert_equal context.ciphers, ciphers + end + end diff --git a/src/test/ruby/ssl/test_helper.rb b/src/test/ruby/ssl/test_helper.rb index d82f012b..f0b73355 100644 --- a/src/test/ruby/ssl/test_helper.rb +++ b/src/test/ruby/ssl/test_helper.rb @@ -1,4 +1,5 @@ require File.expand_path('../test_helper', File.dirname(__FILE__)) +require 'openssl' module SSLTestHelper @@ -7,11 +8,11 @@ module SSLTestHelper PORT = 20443 ITERATIONS = ($0 == __FILE__) ? 100 : 10 - def setup; require 'openssl' + def setup; - @ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048 - @svr_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024 - @cli_key = OpenSSL::PKey::DSA.new TEST_KEY_DSA256 + @ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2 + @svr_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1 + @cli_key = OpenSSL::PKey::DSA.new TEST_KEY_DSA512 @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") @@ -23,9 +24,9 @@ def setup; require 'openssl' [ "keyUsage", "keyEncipherment,digitalSignature", true ], ] now = Time.at(Time.now.to_i) - @ca_cert = issue_cert(@ca, @ca_key, 1, now, now + 3600, ca_exts, nil, nil, OpenSSL::Digest::SHA1.new) - @svr_cert = issue_cert(@svr, @svr_key, 2, now, now + 1800, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) - @cli_cert = issue_cert(@cli, @cli_key, 3, now, now + 1800, ee_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil, not_before: now, not_after: now + 3600) + @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key, not_before: now, not_after: now + 1800) + @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key, not_before: now, not_after: now + 1800) @server = nil end @@ -72,15 +73,15 @@ def start_server0(port0, verify_mode, start_immediately, args = {}, &block); req store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT context = OpenSSL::SSL::SSLContext.new context.cert_store = store - # context.extra_chain_cert = [ ca_cert ] + # context.extra_chain_cert = [ @ca_cert ] context.cert = @svr_cert context.key = @svr_key - context.tmp_dh_callback = proc { OpenSSL::PKey::DH.new(TEST_KEY_DH1024) } + context.tmp_dh_callback = proc { OpenSSL::PKey::DH.new(TEST_KEY_DH1) } context.verify_mode = verify_mode ctx_proc.call(context) if ctx_proc Socket.do_not_reverse_lookup = true - tcp_server = nil + port = port0 begin tcp_server = TCPServer.new("127.0.0.1", port) @@ -92,13 +93,16 @@ def start_server0(port0, verify_mode, start_immediately, args = {}, &block); req ssls = OpenSSL::SSL::SSLServer.new(tcp_server, context) ssls.start_immediately = start_immediately + test_method = caller_locations(1, 1)[0] + test_method = test_method.label.index('block in') ? "#{test_method.path}:#{test_method.lineno}" : test_method.label + begin server = Thread.new do Thread.current.abort_on_exception = true server_loop0(context, ssls, server_proc) end - $stderr.printf("%s started: pid=%d port=%d\n", SSL_SERVER, $$, port) #if $DEBUG + printf("(%s) started: pid=%d port=%d\n", test_method, $$, port) if $VERBOSE block.call(server, port.to_i) ensure @@ -120,10 +124,10 @@ def start_server(verify_mode, start_immediately, args = {}, &block); require 'so ctx = OpenSSL::SSL::SSLContext.new ctx.ciphers = "ADH-AES256-GCM-SHA384" if use_anon_cipher ctx.cert_store = store - #ctx.extra_chain_cert = [ ca_cert ] + # ctx.extra_chain_cert = [ @ca_cert ] ctx.cert = @svr_cert ctx.key = @svr_key - ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 } + ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1 } ctx.verify_mode = verify_mode ctx_proc.call(ctx) if ctx_proc @@ -135,10 +139,12 @@ def start_server(verify_mode, start_immediately, args = {}, &block); require 'so ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) ssls.start_immediately = start_immediately + test_method = caller_locations(3, 1)[0].label + threads = [] begin server = Thread.new do - # Thread.current.abort_on_exception = true + Thread.current.report_on_exception = false begin server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads) ensure @@ -147,17 +153,31 @@ def start_server(verify_mode, start_immediately, args = {}, &block); require 'so end threads.unshift server - $stderr.printf("SSL server started: pid=%d port=%d\n", $$, port) if $DEBUG + printf("SSL server started (#{test_method}): pid=%d port=%d\n", $$, port) if $VERBOSE client = Thread.new do begin block.call(server, port.to_i) ensure + # Stop accepting new connection stop_pipe_w.close + server.join end end threads.unshift client ensure + # Terminate existing connections. If a thread did 'pend', re-raise it. + pend = nil + threads.each { |th| + begin + th.join(5) or th.raise(RuntimeError, "[start_server] thread did not exit in 5 secs") + rescue Test::Unit::PendedError + pend = $! + rescue Exception + warn "#{__method__} (#{test_method}): #{$!.inspect}" if $DEBUG + end + } + raise pend if pend assert_join_threads(threads) end end @@ -184,23 +204,8 @@ def tcp_server_close(thread, tcp_server) tcp_server.close if tcp_server end - def tcp_server_close(thread, tcp_server) - tcp_server.close if (tcp_server) - if thread - thread.join(5) - if thread.alive? - thread.kill - thread.join - flunk("TCPServer was closed and SSLServer is still alive") unless $! - end - end - end if RUBY_VERSION < '1.9.0' || - ( defined? JRUBY_VERSION && JRUBY_VERSION < '1.7.0' ) - private :tcp_server_close - def server_loop0(context, server, server_proc) loop do - ssl = nil begin ssl = server.accept rescue OpenSSL::SSL::SSLError @@ -208,36 +213,41 @@ def server_loop0(context, server, server_proc) end Thread.start do - Thread.current.abort_on_exception = true + Thread.current.report_on_exception = false server_proc.call(context, ssl) end end - rescue Errno::EBADF, IOError, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET + rescue IOError, Errno::EBADF, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET + warn "#{__method__}: #{$!.inspect}" if $DEBUG end def server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads) loop do - ssl = nil begin readable, = IO.select([ssls, stop_pipe_r]) return if readable.include? stop_pipe_r ssl = ssls.accept - rescue OpenSSL::SSL::SSLError + rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET if ignore_listener_error + warn "#{__method__} (retry): #{$!.inspect}" if $DEBUG retry else - raise + warn "#{__method__}: #{$!.inspect}" if $DEBUG end + raise end threads << Thread.start do - # Thread.current.abort_on_exception = true - server_proc.call(ctx, ssl) + Thread.current.report_on_exception = false + begin + server_proc.call(ctx, ssl) + ensure + ssl.close + end end end - rescue Errno::EBADF, IOError, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET => ex + rescue IOError, Errno::EBADF, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET => ex raise(ex) unless ignore_listener_error - puts ex.inspect if $VERBOSE end def server_connect(port, ctx = nil) @@ -270,55 +280,116 @@ def readwrite_loop(context, ssl) ssl.write(line) end rescue IOError, OpenSSL::SSL::SSLError + warn "#{__method__}: #{$!.inspect}" if $DEBUG ensure ssl.close rescue nil end - TEST_KEY_RSA1024 = <<-_end_of_pem_ + TEST_KEY_RSA1 = <<-_end_of_pem_ -----BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx -aKPERYHsk4BPCkE3brtThPWc9kjHEQQ7uf9Y1rbCz0layNqHyywQEVLFmp1cpIt/ -Q3geLv8ZD9pihowKJDyMDiN6ArYUmZczvW4976MU3+l54E6lF/JfFEU5hwIDAQAB -AoGBAKSl/MQarye1yOysqX6P8fDFQt68VvtXkNmlSiKOGuzyho0M+UVSFcs6k1L0 -maDE25AMZUiGzuWHyaU55d7RXDgeskDMakD1v6ZejYtxJkSXbETOTLDwUWTn618T -gnb17tU1jktUtU67xK/08i/XodlgnQhs6VoHTuCh3Hu77O6RAkEA7+gxqBuZR572 -74/akiW/SuXm0SXPEviyO1MuSRwtI87B02D0qgV8D1UHRm4AhMnJ8MCs1809kMQE -JiQUCrp9mQJBANlt2ngBO14us6NnhuAseFDTBzCHXwUUu1YKHpMMmxpnGqaldGgX -sOZB3lgJsT9VlGf3YGYdkLTNVbogQKlKpB8CQQDiSwkb4vyQfDe8/NpU5Not0fII -8jsDUCb+opWUTMmfbxWRR3FBNu8wnym/m19N4fFj8LqYzHX4KY0oVPu6qvJxAkEA -wa5snNekFcqONLIE4G5cosrIrb74sqL8GbGb+KuTAprzj5z1K8Bm0UW9lTjVDjDi -qRYgZfZSL+x1P/54+xTFSwJAY1FxA/N3QPCXCjPh5YqFxAMQs2VVYTfg+t0MEcJD -dPMQD5JX6g5HKnHFg2mZtoXQrWmJSn7p8GJK8yNTopEErA== +MIIJJwIBAAKCAgEArIEJUYZrXhMfUXXdl2gLcXrRB4ciWNEeXt5UVLG0nPhygZwJ +xis8tOrjXOJEpUXUsfgF35pQiJLD4T9/Vp3zLFtMOOQjOR3AxjIelbH9KPyGFEr9 +TcPtsJ24zhcG7RbwOGXR4iIcDaTx+bCLSAd7BjG3XHQtyeepGGRZkGyGUvXjPorH +XP+dQjQnMd09wv0GMZSqQ06PedUUKQ4PJRfMCP+mwjFP+rB3NZuThF0CsNmpoixg +GdoQ591Yrf5rf2Bs848JrYdqJlKlBL6rTFf2glHiC+mE5YRny7RZtv/qIkyUNotV +ce1cE0GFrRmCpw9bqulDDcgKjFkhihTg4Voq0UYdJ6Alg7Ur4JerKTfyCaRGF27V +fh/g2A2/6Vu8xKYYwTAwLn+Tvkx9OTVZ1t15wM7Ma8hHowNoO0g/lWkeltgHLMji +rmeuIYQ20BQmdx2RRgWKl57D0wO/N0HIR+Bm4vcBoNPgMlk9g5WHA6idHR8TLxOr +dMMmTiWfefB0/FzGXBv7DuuzHN3+urdCvG1QIMFQ06kHXhr4rC28KbWIxg+PJGM8 +oGNEGtGWAOvi4Ov+BVsIdbD5Sfyb4nY3L9qqPl6TxRxMWTKsYCYx11jC8civCzOu +yL1z+wgIICJ6iGzrfYf6C2BiNV3BC1YCtp2XsG+AooIxCwjL2CP/54MuRnUCAwEA +AQKCAgAP4+8M0HoRd2d6JIZeDRqIwIyCygLy9Yh7qrVP+/KsRwKdR9dqps73x29c +Pgeexdj67+Lynw9uFT7v/95mBzTAUESsNO+9sizw1OsWVQgB/4kGU4YT5Ml/bHf6 +nApqSqOkPlTgJM46v4f+vTGHWBEQGAJRBO62250q/wt1D1osSDQ/rZ8BxRYiZBV8 +NWocDRzF8nDgtFrpGSS7R21DuHZ2Gb6twscgS6MfkA49sieuTM6gfr/3gavu/+fM +V1Rlrmc65GE61++CSjijQEEdTjkJ9isBd+hjEBhTnnBpOBfEQxOgFqOvU/MYXv/G +W0Q6yWJjUwt3OIcoOImrY5L3j0vERneA1Alweqsbws3fXXMjA+jhLxlJqjPvSAKc +POi7xu7QCJjSSLAzHSDPdmGmfzlrbdWS1h0mrC5YZYOyToLajfnmAlXNNrytnePg +JV9/1136ZFrJyEi1JVN3kyrC+1iVd1E+lWK0U1UQ6/25tJvKFc1I+xToaUbK10UN +ycXib7p2Zsc/+ZMlPRgCxWmpIHmKhnwbO7vtRunnnc6wzhvlQQNHWlIvkyQukV50 +6k/bzWw0M6A98B4oCICIcxcpS3njDlHyL7NlkCD+/OfZp6X3RZF/m4grmA2doebz +glsaNMyGHFrpHkHq19Y63Y4jtBdW/XuBv06Cnr4r3BXdjEzzwQKCAQEA5bj737Nk +ZLA0UgzVVvY67MTserTOECIt4i37nULjRQwsSFiz0AWFOBwUCBJ5N2qDEelbf0Fa +t4VzrphryEgzLz/95ZXi+oxw1liqCHi8iHeU2wSclDtx2jKv2q7bFvFSaH4CKC4N +zBJNfP92kdXuAjXkbK/jWwr64fLNh/2KFWUAmrYmtGfnOjjyL+yZhPxBatztE58q +/T61pkvP9NiLfrr7Xq8fnzrwqGERhXKueyoK6ig9ZJPZ2VTykMUUvNYJJ7OYQZru +EYA3zkuEZifqmjgF57Bgg7dkkIh285TzH3CNf3MCMTmjlWVyHjlyeSPYgISB9Mys +VKKQth+SvYcChQKCAQEAwDyCcolA7+bQBfECs6GXi7RYy2YSlx562S5vhjSlY9Ko +WiwVJWviF7uSBdZRnGUKoPv4K4LV34o2lJpSSTi5Xgp7FH986VdGePe3p4hcXSIZ +NtsKImLVLnEjrmkZExfQl7p0MkcU/LheCf/eEZVp0Z84O54WCs6GRm9wHYIUyrag +9FREqqxTRVNhQQ2EDVGq1slREdwB+aygE76axK/qosk0RaoLzGZiMn4Sb8bpJxXO +mee+ftq5bayVltfR0DhC8eHkcPPFeQMll1g+ML7HbINwHTr01ONm3cFUO4zOLBOO +ws/+vtNfiv6S/lO1RQSRoiApbENBLdSc3V8Cy70PMQKCAQBOcZN4uP5gL5c+KWm0 +T1KhxUDnSdRPyAwY/xC7i7qlullovvlv4GK0XUot03kXBkUJmcEHvF5o6qYtCZlM +g/MOgHCHtF4Upl5lo1M0n13pz8PB4lpBd+cR1lscdrcTp4Y3bkf4RnmppNpXA7kO +ZZnnoVWGE620ShSPkWTDuj0rvxisu+SNmClqRUXWPZnSwnzoK9a86443efF3fs3d +UxCXTuxFUdGfgvXo2XStOBMCtcGSYflM3fv27b4C13mUXhY0O2yTgn8m9LyZsknc +xGalENpbWmwqrjYl8KOF2+gFZV68FZ67Bm6otkJ4ta80VJw6joT9/eIe6IA34KIw +G+ktAoIBAFRuPxzvC4ZSaasyX21l25mQbC9pdWDKEkqxCmp3VOyy6R4xnlgBOhwS +VeAacV2vQyvRfv4dSLIVkkNSRDHEqCWVlNk75TDXFCytIAyE54xAHbLqIVlY7yim +qHVB07F/FC6PxdkPPziAAU2DA5XVedSHibslg6jbbD4jU6qiJ1+hNrAZEs+jQC+C +n4Ri20y+Qbp0URb2+icemnARlwgr+3HjzQGL3gK4NQjYNmDBjEWOXl9aWWB90FNL +KahGwfAhxcVW4W56opCzwR7nsujV4eDXGba83itidRuQfd5pyWOyc1E86TYGwD/b +79OkEElv6Ea8uXTDVS075GmWATRapQECggEAd9ZAbyT+KouTfi2e6yLOosxSZfns +eF06QAJi5n9GOtdfK5fqdmHJqJI7wbubCnd0oxPeL71lRjrOAMXufaQRdZtfXSMn +B1TljteNrh1en5xF451rCPR/Y6tNKBvIKnhy1waO27/vA+ovXrm17iR9rRuGZ29i +IurlKA6z/96UdrSdpqITTCyTjSOBYg34f49ueGjlpL4+8HJq2wor4Cb1Sbv8ErqA +bsQ/Jz+KIGUiuFCfNa6d6McPRXIrGgzpprXgfimkV3nj49QyrnuCF/Pc4psGgIaN +l3EiGXzRt/55K7DQVadtbcjo9zREac8QnDD6dS/gOfJ82L7frQfMpNWgQA== -----END RSA PRIVATE KEY----- _end_of_pem_ - TEST_KEY_RSA2048 = <<-_end_of_pem_ + TEST_KEY_RSA2 = <<-_end_of_pem_ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAuV9ht9J7k4NBs38jOXvvTKY9gW8nLICSno5EETR1cuF7i4pN -s9I1QJGAFAX0BEO4KbzXmuOvfCpD3CU+Slp1enenfzq/t/e/1IRW0wkJUJUFQign -4CtrkJL+P07yx18UjyPlBXb81ApEmAB5mrJVSrWmqbjs07JbuS4QQGGXLc+Su96D -kYKmSNVjBiLxVVSpyZfAY3hD37d60uG+X8xdW5v68JkRFIhdGlb6JL8fllf/A/bl -NwdJOhVr9mESHhwGjwfSeTDPfd8ZLE027E5lyAVX9KZYcU00mOX+fdxOSnGqS/8J -DRh0EPHDL15RcJjV2J6vZjPb0rOYGDoMcH+94wIDAQABAoIBAAzsamqfYQAqwXTb -I0CJtGg6msUgU7HVkOM+9d3hM2L791oGHV6xBAdpXW2H8LgvZHJ8eOeSghR8+dgq -PIqAffo4x1Oma+FOg3A0fb0evyiACyrOk+EcBdbBeLo/LcvahBtqnDfiUMQTpy6V -seSoFCwuN91TSCeGIsDpRjbG1vxZgtx+uI+oH5+ytqJOmfCksRDCkMglGkzyfcl0 -Xc5CUhIJ0my53xijEUQl19rtWdMnNnnkdbG8PT3LZlOta5Do86BElzUYka0C6dUc -VsBDQ0Nup0P6rEQgy7tephHoRlUGTYamsajGJaAo1F3IQVIrRSuagi7+YpSpCqsW -wORqorkCgYEA7RdX6MDVrbw7LePnhyuaqTiMK+055/R1TqhB1JvvxJ1CXk2rDL6G -0TLHQ7oGofd5LYiemg4ZVtWdJe43BPZlVgT6lvL/iGo8JnrncB9Da6L7nrq/+Rvj -XGjf1qODCK+LmreZWEsaLPURIoR/Ewwxb9J2zd0CaMjeTwafJo1CZvcCgYEAyCgb -aqoWvUecX8VvARfuA593Lsi50t4MEArnOXXcd1RnXoZWhbx5rgO8/ATKfXr0BK/n -h2GF9PfKzHFm/4V6e82OL7gu/kLy2u9bXN74vOvWFL5NOrOKPM7Kg+9I131kNYOw -Ivnr/VtHE5s0dY7JChYWE1F3vArrOw3T00a4CXUCgYEA0SqY+dS2LvIzW4cHCe9k -IQqsT0yYm5TFsUEr4sA3xcPfe4cV8sZb9k/QEGYb1+SWWZ+AHPV3UW5fl8kTbSNb -v4ng8i8rVVQ0ANbJO9e5CUrepein2MPL0AkOATR8M7t7dGGpvYV0cFk8ZrFx0oId -U0PgYDotF/iueBWlbsOM430CgYEAqYI95dFyPI5/AiSkY5queeb8+mQH62sdcCCr -vd/w/CZA/K5sbAo4SoTj8dLk4evU6HtIa0DOP63y071eaxvRpTNqLUOgmLh+D6gS -Cc7TfLuFrD+WDBatBd5jZ+SoHccVrLR/4L8jeodo5FPW05A+9gnKXEXsTxY4LOUC -9bS4e1kCgYAqVXZh63JsMwoaxCYmQ66eJojKa47VNrOeIZDZvd2BPVf30glBOT41 -gBoDG3WMPZoQj9pb7uMcrnvs4APj2FIhMU8U15LcPAj59cD6S6rWnAxO8NFK7HQG -4Jxg3JNNf8ErQoCHb1B3oVdXJkmbJkARoDpBKmTCgKtP8ADYLmVPQw== +MIIJKAIBAAKCAgEA1HUbx825tG7+/ulC5DpDogzXqM2/KmeCwGXZY4XjiWa+Zj7b +ECkZwQh7zxFUsPixGqQKJSyFwCogdaPzYTRNtqKKaw/IWS0um1PTn4C4/9atbIsf +HVKu/fWg4VrZL+ixFIZxa8Z6pvTB2omMcx+uEzbXPsO01i1pHf7MaWBxUDGFyC9P +lASJBfFZAf2Ar1H99OTS4SP+gxM9Kk5tcc22r8uFiqqbhJmQNSDApdHvT1zSZxAc +T1BFEZqfmR0B0UegPyJc/9hW0dYpB9JjR29UaZRSta3LUMpqltoOF5bzaKVgMuBm +Qy79xJ71LjGp8bKhgRaWXyPsDzAC0MQlOW6En0v8LK8fntivJEvw9PNOMcZ8oMTn +no0NeVt32HiQJW8LIVo7dOLVFtguSBMWUVe8mdKbuIIULD6JlSYke9Ob6andUhzO +U79m/aRWs2yjD6o5QAktjFBARdPgcpTdWfppc8xpJUkQgRmVhINoIMT9W6Wl898E +P4aPx6mRV/k05ellN3zRgd9tx5dyNuj3RBaNmR47cAVvGYRQgtH9bQYs6jtf0oer +A5yIYEKspNRlZZJKKrQdLflQFOEwjQJyZnTk7Mp0y21wOuEGgZBexew55/hUJDC2 +mQ8CqjV4ki/Mm3z6Cw3jXIMNBJkH7oveBGSX0S9bF8A/73oOCU3W/LkORxECAwEA +AQKCAgBLK7RMmYmfQbaPUtEMF2FesNSNMV72DfHBSUgFYpYDQ4sSeiLgMOqf1fSY +azVf+F4RYwED7iDUwRMDDKNMPUlR2WjIQKlOhCH9a0dxJAZQ3xA1W3QC2AJ6cLIf +ihlWTip5bKgszekPsYH1ZL2A7jCVM84ssuoE7cRHjKOelTUCfsMq9TJe2MvyglZP +0fX6EjSctWm3pxiiH+iAU4d9wJ9my8fQLFUiMYNIiPIguYrGtbzsIlMh7PDDLcZS +UmUWOxWDwRDOpSjyzadu0Q23dLiVMpmhFoDdcQENptFdn1c4K2tCFQuZscKwEt4F +HiVXEzD5j5hcyUT4irA0VXImQ+hAH3oSDmn7wyHvyOg0bDZpUZXEHXb83Vvo54/d +Fb4AOUva1dwhjci8CTEMxCENMy/CLilRv46AeHbOX8KMPM7BnRSJPptvTTh/qB9C +HI5hxfkO+EOYnu0kUlxhJfrqG86H4IS+zA8HWiSEGxQteMjUQfgJoBzJ94YChpzo +ePpKSpjxxl1PNNWKxWM3yUvlKmI2lNl6YNC8JpF2wVg4VvYkG7iVjleeRg21ay89 +NCVMF98n3MI5jdzfDKACnuYxg7sw+gjMy8PSoFvQ5pvHuBBOpa8tho6vk7bLJixT +QY5uXMNQaO6OwpkBssKpnuXhIJzDhO48nSjJ5nUEuadPH1nGwQKCAQEA7twrUIMi +Vqze/X6VyfEBnX+n3ZyQHLGqUv/ww1ZOOHmSW5ceC4GxHa8EPDjoh9NEjYffwGq9 +bfQh9Gntjk5gFipT/SfPrIhbPt59HthUqVvOGgSErCmn0vhsa0+ROpVi4K2WHS7O +7SEwnoCWd6p1omon2olVY0ODlMH4neCx/ZuKV8SRMREubABlL8/MLp37AkgKarTY +tewd0lpaZMvsjOhr1zVCGUUBxy87Fc7OKAcoQY8//0r8VMH7Jlga7F2PKVPzqRKf +tjeW5jMAuRxTqtEdIeclJZwvUMxvb23BbBE+mtvKpXv69TB3DK8T1YIkhW2CidZW +lad4MESC+QFNbQKCAQEA47PtULM/0ZFdE+PDDHOa2kJ2arm94sVIqF2168ZLXR69 +NkvCWfjkUPDeejINCx7XQgk0d/+5BCvrJpcM7lE4XfnYVNtPpct1el6eTfaOcPU8 +wAMsnq5n9Mxt02U+XRPtEqGk+lt0KLPDDSG88Z7jPmfftigLyPH6i/ZJyRUETlGk +rGnWSx/LFUxQU5aBa2jUCjKOKa+OOk2jGg50A5Cmk26v9sA/ksOHisMjfdIpZc9P +r4R0IteDDD5awlkWTF++5u1GpgU2yav4uan0wzY8OWYFzVyceA6+wffEcoplLm82 +CPd/qJOB5HHkjoM+CJgfumFxlNtdowKvKNUxpoQNtQKCAQEAh3ugofFPp+Q0M4r6 +gWnPZbuDxsLIR05K8vszYEjy4zup1YO4ygQNJ24fM91/n5Mo/jJEqwqgWd6w58ax +tRclj00BCMXtGMrbHqTqSXWhR9LH66AGdPTHuXWpYZDnKliTlic/z1u+iWhbAHyl +XEj2omIeKunc4gnod5cyYrKRouz3omLfi/pX33C19FGkWgjH2HpuViowBbhhDfCr +9yJoEWC/0njl/hlTMdzLYcpEyxWMMuuC/FZXG+hPgWdWFh3XVzTEL3Fd3+hWEkp5 +rYWwu2ITaSiHvHaDrAvZZVXW8WoynXnvzr+tECgmTq57zI4eEwSTl4VY5VfxZ0dl +FsIzXQKCAQBC07GYd6MJPGJWzgeWhe8yk0Lxu6WRAll6oFYd5kqD/9uELePSSAup +/actsbbGRrziMpVlinWgVctjvf0bjFbArezhqqPLgtTtnwtS0kOnvzGfIM9dms4D +uGObISGWa5yuVSZ4G5MRxwA9wGMVfo4u6Iltin868FmZ7iRlkXd8DNYJi95KmgAe +NhF1FrzQ6ykf/QpgDZfuYI63vPorea6JonieMHn39s622OJ3sNBZguheGL+E4j8h +vsMgOskijQ8X8xdC7lDQC1qqEsk06ZvvNJQLW1zIl3tArhjHjPp5EEaJhym+Ldx3 +UT3E3Zu9JfhZ2PNevqrShp0lnLw/pI3pAoIBAAUMz5Lj6V9ftsl1pTa8WDFeBJW0 +Wa5AT1BZg/ip2uq2NLPnA5JWcD+v682fRSvIj1pU0DRi6VsXlzhs+1q3+sgqiXGz +u2ArFylh8TvC1gXUctXKZz/M3Rqr6aSNoejUGLmvHre+ja/k6Zwmu6ePtB7dL50d +6+xMTYquS4gLbrbSLcEu3iBAAnvRLreXK4KguPxaBdICB7v7epdpAKe3Z7hp/sst +eJj1+6KRdlcmt8fh5MPkBBXa6I/9XGmX5UEo7q4wAxeM9nuFWY3watz/EO9LiO6P +LmqUSWL65m4cX0VZPvhYEsHppKi1eoWGlHqS4Af5+aIXi2alu2iljQFeA+Q= -----END RSA PRIVATE KEY----- _end_of_pem_ @@ -344,11 +415,19 @@ def readwrite_loop(context, ssl) -----END DSA PRIVATE KEY----- _end_of_pem_ - TEST_KEY_DH1024 = <<-_end_of_pem_ + TEST_KEY_DH1 = <<-_end_of_pem_ -----BEGIN DH PARAMETERS----- -MIGHAoGBAKnKQ8MNK6nYZzLrrcuTsLxuiJGXoOO5gT+tljOTbHBuiktdMTITzIY0 -pFxIvjG05D7HoBZQfrR0c92NGWPkAiCkhQKB8JCbPVzwNLDy6DZ0pmofDKrEsYHG -AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC +MIICCAKCAgEAvRzXYxY6L2DjeYmm1eowtMDu1it3j+VwFr6s6PRWzc1apMtztr9G +xZ2mYndUAJLgNLO3n2fUDCYVMB6ZkcekW8Siocof3xWiMA6wqZ6uw0dsE3q7ZX+6 +TLjgSjaXeGvjutvuEwVrFeaUi83bMgfXN8ToxIQVprIF35sYFt6fpbFATKfW7qqi +P1pQkjmCskU4tztaWvlLh0qg85wuQGnpJaQT3gS30378i0IGbA0EBvJcSpTHYbLa +nsdI9bfN/ZVgeolVMNMU9/n8R8vRhNPcHuciFwaqS656q+HavCIyxw/LfjSwwFvR +TngCn0wytRErkzFIXnRKckh8/BpI4S+0+l1NkOwG4WJ55KJ/9OOdZW5o/QCp2bDi +E0JN1EP/gkSom/prq8JR/yEqtsy99uc5nUxPmzv0IgdcFHZEfiQU7iRggEbx7qfQ +Ve55XksmmJInmpCy1bSabAEgIKp8Ckt5KLYZ0RgTXUhcEpsxEo6cuAwoSJT5o4Rp +yG3xow2ozPcqZkvb+d2CHj1sc54w9BVFAjVANEKmRil/9WKz14bu3wxEhOPqC54n +QojjLcoXSoT66ZUOQnYxTSiLtzoKGPy8cAVPbkBrXz2u2sj5gcvr1JjoGjdHm9/3 +qnqC8fsTz8UndKNIQC337o4K0833bQMzRGl1/qjbAPit2B7E3b6xTZMCAQI= -----END DH PARAMETERS----- _end_of_pem_ diff --git a/src/test/ruby/ssl/test_ocsp.rb b/src/test/ruby/ssl/test_ocsp.rb index 6f0f4851..226fff1a 100644 --- a/src/test/ruby/ssl/test_ocsp.rb +++ b/src/test/ruby/ssl/test_ocsp.rb @@ -14,32 +14,32 @@ def setup now = Time.now ca_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") - @ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024 + @ca_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1 ca_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] - @ca_cert = issue_cert(ca_subj, @ca_key, 1, now, now+1800, ca_exts, nil, nil, OpenSSL::Digest::SHA1.new) + @ca_cert = issue_cert(ca_subj, @ca_key, 1, ca_exts, nil, nil, not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) cert_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA2") - @cert_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024 + @cert_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1 cert_exts = [ ["basicConstraints", "CA:TRUE", true], ["keyUsage", "cRLSign,keyCertSign", true], ] - @cert = issue_cert(cert_subj, @cert_key, 5, now, now+1800, cert_exts, @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @cert = issue_cert(cert_subj, @cert_key, 5, cert_exts, @ca_cert, @ca_key, not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) cert2_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCert") - @cert2_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1024 + @cert2_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA1 cert2_exts = [] - @cert2 = issue_cert(cert2_subj, @cert2_key, 10, now, now+1800, cert2_exts, @cert, @cert_key, OpenSSL::Digest::SHA1.new) + @cert2 = issue_cert(cert2_subj, @cert2_key, 10, cert2_exts, @cert, @cert_key, not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) ocsp_subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCAOCSP") - @ocsp_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048 + @ocsp_key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2 ocsp_exts = [ ["extendedKeyUsage", "OCSPSigning", true], ] - @ocsp_cert = issue_cert(ocsp_subj, @ocsp_key, 100, now, now+1800, ocsp_exts, @cert, @cert_key, OpenSSL::Digest::SHA1.new) + @ocsp_cert = issue_cert(ocsp_subj, @ocsp_key, 100, ocsp_exts, @cert, @cert_key, not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) end def test_new_certificate_id @@ -60,7 +60,7 @@ def test_certificate_id_issuer_name_hash def test_certificate_id_issuer_key_hash cid = OpenSSL::OCSP::CertificateId.new(@cert, @ca_cert) assert_equal OpenSSL::Digest::SHA1.hexdigest(OpenSSL::ASN1.decode(@ca_cert.to_der).value[0].value[6].value[1].value), cid.issuer_key_hash - assert_equal "d1fef9fbf8ae1bc160cbfa03e2596dd873089213", cid.issuer_key_hash + assert_equal "6eef4d0b438009bda21dd7f38a92472bc9bbfb3b", cid.issuer_key_hash end def test_certificate_id_hash_algorithm diff --git a/src/test/ruby/ssl/test_session.rb b/src/test/ruby/ssl/test_session.rb index 14678fe9..71d39a3f 100644 --- a/src/test/ruby/ssl/test_session.rb +++ b/src/test/ruby/ssl/test_session.rb @@ -7,7 +7,7 @@ class TestSSLSession < TestCase def test_session start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| sock = TCPSocket.new("127.0.0.1", port) - ctx = OpenSSL::SSL::SSLContext.new("TLSv1") + ctx = OpenSSL::SSL::SSLContext.new("TLSv1_2") ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true ssl.connect @@ -30,6 +30,27 @@ def test_session end end + def test_alpn_protocol_selection_ary + advertised = ["h2", "http/1.1"] + ctx_proc = Proc.new { |ctx| + ctx.alpn_select_cb = -> (protocols) { + assert_equal Array, protocols.class + assert_equal advertised, protocols + protocols.first + } + } + start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, ctx_proc: ctx_proc) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ctx = OpenSSL::SSL::SSLContext.new("TLSv1_2") + ctx.alpn_protocols = advertised + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.sync_close = true + ssl.connect + assert_equal("h2", ssl.alpn_protocol) + ssl.puts "abc"; assert_equal "abc\n", ssl.gets + end + end + def test_exposes_session_error OpenSSL::SSL::Session::SessionError end diff --git a/src/test/ruby/ssl/test_socket.rb b/src/test/ruby/ssl/test_socket.rb index c7dc3a80..492e279b 100644 --- a/src/test/ruby/ssl/test_socket.rb +++ b/src/test/ruby/ssl/test_socket.rb @@ -85,22 +85,21 @@ def test_ssl_sysread_blocking_error end end if RUBY_VERSION > '2.2' - def test_read_nonblock_no_exception - ssl_pair do |s1, s2| - assert_equal :wait_readable, eval('s2.read_nonblock 10, exception: false') - s1.write "abc\ndef\n" - IO.select [ s2 ] - ret = eval('s2.read_nonblock 2, exception: false') - assert_equal "ab", ret - assert_equal "c\n", s2.gets - ret = eval('s2.read_nonblock 10, exception: false') - assert_equal("def\n", ret) - s1.close - sleep 0.1 - opts = { :exception => false } - assert_equal nil, s2.read_nonblock(10, opts) - end - end if RUBY_VERSION > '2.2' + def test_read_nonblock_without_session + start_server(OpenSSL::SSL::VERIFY_PEER, false) { |_, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true + + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) + ssl.write("abc\n") + IO.select [ssl] + assert_equal('a', ssl.read_nonblock(1)) + assert_equal("bc\n", ssl.read_nonblock(100)) + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) + ssl.close + } + end def test_connect_non_connected; require 'socket' socket = OpenSSL::SSL::SSLSocket.new(Socket.new(:INET, :STREAM)) @@ -115,14 +114,17 @@ def test_connect_non_connected; require 'socket' end if RUBY_VERSION > '2.2' def test_connect_nonblock - ssl_server = server + host = "127.0.0.1"; port = 0 + server = TCPServer.new(host, port) + ssl_server = OpenSSL::SSL::SSLServer.new(server, OpenSSL::SSL::SSLContext.new) + thread = Thread.new do ssl_server.accept.tap { ssl_server.close } end host = "127.0.0.1" ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" + ctx.ciphers = "AES" client = TCPSocket.new host, server_port(ssl_server) client = OpenSSL::SSL::SSLSocket.new(client, ctx) begin @@ -150,18 +152,18 @@ def test_inherited_socket; require 'socket' private - def server; require 'socket' + def server(ssl_version: nil); require 'socket' host = "127.0.0.1"; port = 0 ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" + ctx.ssl_version = ssl_version if ssl_version server = TCPServer.new(host, port) OpenSSL::SSL::SSLServer.new(server, ctx) end - def client(port); require 'socket' + def client(port, ssl_version: nil); require 'socket' host = "127.0.0.1" ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" + ctx.ssl_version = ssl_version if ssl_version client = TCPSocket.new(host, port) ssl = OpenSSL::SSL::SSLSocket.new(client, ctx) ssl.connect @@ -174,11 +176,11 @@ def server_port(ssl_server = server) end def ssl_pair - ssl_server = server + ssl_server = server ssl_version: 'TLSv1_2' thread = Thread.new do ssl_server.accept.tap { ssl_server.close } end - ssl_client = client server_port(ssl_server) + ssl_client = client server_port(ssl_server), ssl_version: 'TLSv1_2' ssl_socket = thread.value if block_given? begin diff --git a/src/test/ruby/ssl/test_ssl.rb b/src/test/ruby/ssl/test_ssl.rb index 9088b662..91247d5e 100644 --- a/src/test/ruby/ssl/test_ssl.rb +++ b/src/test/ruby/ssl/test_ssl.rb @@ -7,8 +7,6 @@ class TestSSL < TestCase def test_context_default_constants assert OpenSSL::SSL::SSLContext::DEFAULT_PARAMS - assert_equal 'SSLv23', OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ssl_version] - # assert_equal "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] assert_equal OpenSSL::SSL::VERIFY_PEER, OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:verify_mode] assert OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE @@ -40,8 +38,8 @@ def test_post_connection_check ["subjectAltName","DNS:localhost.localdomain",false], ["subjectAltName","IP:127.0.0.1",false], ] - @svr_cert = issue_cert(@svr, @svr_key, 4, now, now + 1800, exts, - @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key, + not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -55,8 +53,8 @@ def test_post_connection_check 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") + refute OpenSSL::SSL.verify_certificate_identity(cert, "localhost") + refute OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com") end now = Time.now @@ -64,8 +62,8 @@ def test_post_connection_check [ "keyUsage", "keyEncipherment,digitalSignature", true ], [ "subjectAltName", "DNS:*.localdomain", false ], ] - @svr_cert = issue_cert(@svr, @svr_key, 5, now, now + 1800, exts, - @ca_cert, @ca_key, OpenSSL::Digest::SHA1.new) + @svr_cert = issue_cert(@svr, @svr_key, 5, exts, @ca_cert, @ca_key, + not_before: now, not_after: now + 1800, digest: OpenSSL::Digest::SHA1.new) start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -76,9 +74,9 @@ def test_post_connection_check 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") + refute OpenSSL::SSL.verify_certificate_identity(cert, "127.0.0.1") + refute OpenSSL::SSL.verify_certificate_identity(cert, "localhost") + refute OpenSSL::SSL.verify_certificate_identity(cert, "foo.example.com") end end @@ -98,42 +96,74 @@ def test_post_connect_check_with_anon_ciphers } end - def test_ssl_version_tlsv1 + def test_parallel + start_server(OpenSSL::SSL::VERIFY_PEER, true) { |_, 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_ssl_version_tlsv1_2 ctx_proc = Proc.new do |ctx| - ctx.ssl_version = "TLSv1" + ctx.ssl_version = "TLSv1_2" end start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect - assert_equal("TLSv1", ssl.ssl_version) + assert_equal("TLSv1.2", ssl.ssl_version) ssl.close end end - def test_ssl_version_tlsv1_1 + def test_ssl_version_tlsv1_3 ctx_proc = Proc.new do |ctx| - ctx.ssl_version = "TLSv1_1" + ctx.ssl_version = "TLSv1_3" end start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect - assert_equal("TLSv1.1", ssl.ssl_version) + assert_equal("TLSv1.3", ssl.ssl_version) ssl.close end end - def test_ssl_version_tlsv1_2 - ctx_proc = Proc.new do |ctx| - ctx.ssl_version = "TLSv1_2" - end - start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| - sock = TCPSocket.new("127.0.0.1", port) - ssl = OpenSSL::SSL::SSLSocket.new(sock) - ssl.connect - assert_equal("TLSv1.2", ssl.ssl_version) - ssl.close + MAX_SSL_VERSION = "TLSv1.3" + + [ + [OpenSSL::SSL::TLS1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1,)"], + [OpenSSL::SSL::TLS1_1_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.1,)"], + [OpenSSL::SSL::TLS1_2_VERSION, nil, MAX_SSL_VERSION, "(TLSv1.2,)"], + [nil, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(,TLSv1.2)"], + [OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_2_VERSION, "TLSv1.2", "(TLSv1,TLSv1.2)"] + ].each do |min_version, max_version, expected_version, desc| + define_method("test_ssl_minmax_#{desc}") do + ctx_proc = Proc.new do |ctx| + ctx.min_version = min_version unless min_version.nil? + ctx.max_version = max_version unless max_version.nil? + end + start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| + sock = TCPSocket.new("127.0.0.1", port) + ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.connect + assert_equal(expected_version, ssl.ssl_version) + ssl.close + end end end @@ -143,21 +173,13 @@ def test_read_nonblock_would_block ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.connect - if defined? OpenSSL::SSL::SSLErrorWaitReadable - begin - ssl.read_nonblock(2) - fail 'read would block error not raised!' - rescue OpenSSL::SSL::SSLErrorWaitReadable => e - assert_equal 'read would block', e.message - end - else - begin - ssl.read_nonblock(2) - fail 'read would block error not raised!' - rescue => e - assert_equal 'read would block', e.message - end + begin + ssl.read_nonblock(2) + fail 'read would block error not raised!' + rescue OpenSSL::SSL::SSLErrorWaitReadable => e + assert_equal 'read would block', e.message end + if RUBY_VERSION > '2.2' result = eval "ssl.read_nonblock(5, 'buff', exception: false)" assert_equal :wait_readable, result @@ -169,34 +191,18 @@ def test_read_nonblock_would_block end end - def test_connect_nonblock_would_block - start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, true) do |server, port| + def test_read_nonblock_without_session + start_server0(PORT, OpenSSL::SSL::VERIFY_NONE, false) do |server, port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) + ssl.sync_close = true - if defined? OpenSSL::SSL::SSLErrorWaitReadable - begin - ssl.connect_nonblock - fail 'read would block error not raised!' - rescue OpenSSL::SSL::SSLErrorWaitReadable => e - assert_equal 'read would block', e.message - end - else - begin - ssl.connect_nonblock - fail 'read would block error not raised!' - rescue => e - assert_equal 'read would block', e.message - end - end - - if RUBY_VERSION > '2.2' - result = eval "ssl.connect_nonblock(exception: false)" - assert_equal :wait_readable, result - end - result = ssl.connect_nonblock(:exception => false) - assert_equal :wait_readable, result - + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) + ssl.write("abc\n") + IO.select [ssl] + assert_equal('a', ssl.read_nonblock(1)) + assert_equal("bc\n", ssl.read_nonblock(100)) + assert_equal :wait_readable, ssl.read_nonblock(100, exception: false) ssl.close end end @@ -217,13 +223,15 @@ def test_renegotiation_cb def test_tlsext_hostname return unless OpenSSL::SSL::SSLSocket.instance_methods.include?(:hostname) - ctx_proc = Proc.new do |ctx, ssl| - foo_ctx = ctx.dup + fooctx = OpenSSL::SSL::SSLContext.new + fooctx.cert = @cli_cert + fooctx.key = @cli_key + ctx_proc = Proc.new do |ctx, ssl| ctx.servername_cb = Proc.new do |ssl2, hostname| case hostname when 'foo.example.com' - foo_ctx + fooctx when 'bar.example.com' nil else @@ -251,4 +259,277 @@ def test_tlsext_hostname end end + CUSTOM_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:" + + "ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:" + + "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:" + + "ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:" + + "DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:" + + "DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:" + + "AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:" + + "!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" + + def test_set_custom_params + ops = OpenSSL::SSL::OP_ALL + ops &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) + ops |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) + ops |= OpenSSL::SSL::OP_NO_SSLv2 + ops |= OpenSSL::SSL::OP_NO_SSLv3 + + params = { :ssl_version => "TLSv1_2", :ciphers => CUSTOM_CIPHERS, :options => ops } + params.merge!( :verify_mode => OpenSSL::SSL::VERIFY_NONE ) + + ctx_proc = Proc.new { |ctx, ssl| ctx.set_params(params) } + + start_server(OpenSSL::SSL::VERIFY_NONE, true, :ctx_proc => ctx_proc) do |server, port| + context = OpenSSL::SSL::SSLContext.new.tap { |ctx| ctx.set_params(params) } + socket = TCPSocket.new("127.0.0.1", port) + client = OpenSSL::SSL::SSLSocket.new socket, context + + client.connect + + client.close rescue nil + end + end + + LEAF_CERTIFICATE = OpenSSL::X509::Certificate.new <<-EOF +-----BEGIN CERTIFICATE----- +MIIFKDCCBBCgAwIBAgISBP+uKglvwxGq302F+yCqxvnXMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yMTA4MTEwOTAxMzdaFw0yMTExMDkwOTAxMzVaMBwxGjAYBgNVBAMT +EWdlb2lwLmVsYXN0aWMuZGV2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtazxd/2FWW1O5evHkDnPi4vcZJDFxs8V0tlI2ppf/OTymlBHMbzBE3BsUEP7 +SkT+6kPnqQoy85S66zT4f2XyQfSWUZJeMPMcODl5P0SXEBlKv+ElRYvrsUpuc0ZH +ZTIM3+ueUY5M3Xmo9ao+I5evahr4Pf1laRWhHRLzFdKiMn7r1/qXf+PzKqZlzLng +cULtVpCTZlOk7CwrsAxwTYdFe1Z0b2ebKs793Ghag2V3D2YtCMuqLa1GP1sBsFRT +v1XPehXb5UOWffp3RJnUoG3n7K5cPI6G+fUAGRF3wxKuH+PYyW6/irb5+v4CVVSi +z+f29zDYeOc+baWGWFfymktslwIDAQABo4ICTDCCAkgwDgYDVR0PAQH/BAQDAgWg +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G +A1UdDgQWBBQ23ntd4n192uVjxt9C0B18QYWMyzAfBgNVHSMEGDAWgBQULrMXt1hW +y65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6 +Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iu +b3JnLzAcBgNVHREEFTATghFnZW9pcC5lbGFzdGljLmRldjBMBgNVHSAERTBDMAgG +BmeBDAECATA3BgsrBgEEAYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3Bz +LmxldHNlbmNyeXB0Lm9yZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1AH0+8viP +/4hVaCTCwMqeUol5K8UOeAl/LmqXaJl+IvDXAAABezSpB0oAAAQDAEYwRAIgC5B1 +huzXAJCbtfWO5GGMVj930XNoNPGQj6o8yJfMQnMCIBdlncSV2rymFbZG7Q2PSAim +7/PkW/2qD3Vt8Ald8u3DAHcARJRlLrDuzq/EQAfYqP4owNrmgr7YyzG1P9MzlrW2 +gagAAAF7NKkJHAAABAMASDBGAiEAnWU3nUNjdHdrE62v0y45WDLj6eyfXkIxAh9Z +GAA2wJACIQDtKZNFze3mAj7pE6m3AZMfnq4N0VvO2Ahr0HbpN/xWzDANBgkqhkiG +9w0BAQsFAAOCAQEABWFFyolbYnyqDA8ckU0Lm7btCM78CeljjKxVCGTqhlntJhhH +NBJcRArzCBkres7Z4yySiJ1vSUXNVvGITVCi2d/zJ5SxBDoT5v8IjEb98KH//9u3 +Jb1CfuEADhnEUXjyf4GeIiTHtdKX36jGwTRO3YIa52G6HONbOnQBgcwn8FpYJdIj +3C58o5AxWRcVVQbaCFxjGcCLSUSQsJxzilsYE+xVqc+d5GftG3Nmy6l3Ht84693n +UwMrb/rlsQC163gtdVEN/GFCeLU+UfFGuSeCmUM3SmAIVfD/yjLvisVpf70pV0Jg +p1Px196NI71smu8LxrhX78ErTrR4GpDkx4W+uw== +-----END CERTIFICATE----- + EOF + + + EXPIRED_DST_ROOT_CA_X3 = OpenSSL::X509::Certificate.new <<-EOF +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + EOF + + require 'time' + VERIFY_EXPIRED_TIME = Time.parse("2021/10/20 09:10:00") + + def test_cert_verify_expired1_lets_encrypt_cross_signed_root + # reproducer for https://github.com/jruby/jruby-openssl/issues/236 + # + # In this reproducer we have a leaf certificate with two possible chains: + # a) leaf -> intermediate cert A -> ISRG Root X1 cross-signed by (expired) DST ROOT CA X3 -> (expired) DST ROOT CA X3 + # b) leaf -> intermediate cert B -> ISRG Root X1 + # JRuby will produce chain a) causing an error, while CRuby produces a valid chain b) + + root_bundle = [ + # Expired DST ROOT CA X3 + EXPIRED_DST_ROOT_CA_X3, + # active ISRG Root X1 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrgrootx1.pem', __FILE__))), + # ISRG Root X1 cross-signed by (expired) DST ROOT CA X3 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrg-root-x1-cross-signed.pem', __FILE__))) + ] + + cert_store = OpenSSL::X509::Store.new + cert_store.time = VERIFY_EXPIRED_TIME + root_bundle.each { |cert| cert_store.add_cert cert } + + # the endpoint will send the leaf node + these two intermediate certs + chain = [ + # Intermediate cert from expired CA + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/lets-encrypt-r3-cross-signed.pem', __FILE__))), + # Valid Intermediate cert + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/lets-encrypt-r3.pem', __FILE__))), + ] + + # let's try to validate the leaf+chain against the root bundle + ok = cert_store.verify(LEAF_CERTIFICATE, chain) + + # pp cert_store.chain if $VERBOSE + + assert_equal true, ok + assert_equal 'ok', cert_store.error_string + assert_equal ["/CN=geoip.elastic.dev", + "/C=US/O=Let's Encrypt/CN=R3", + "/C=US/O=Internet Security Research Group/CN=ISRG Root X1"], + cert_store.chain.map { |cert| cert.subject.to_s } + + # 0.10.7 + # [#, + # issuer=#, + # serial=#, + # not_before=2021-08-11 09:01:37 UTC, + # not_after=2021-11-09 09:01:35 UTC>, + # #, + # issuer=#, + # serial=#, + # not_before=2020-10-07 19:21:40 UTC, + # not_after=2021-09-29 19:21:40 UTC>, + # #, + # issuer=#, + # serial=#, + # not_before=2000-09-30 21:12:19 UTC, + # not_after=2021-09-30 14:01:15 UTC>] + # 10 + # certificate has expired + end + + def test_cert_verify_expired2_lets_encrypt_cross_signed_intermediate + + root_bundle = [ + # Expired DST ROOT CA X3 + EXPIRED_DST_ROOT_CA_X3, + # active ISRG Root X1 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrgrootx1.pem', __FILE__))), + # ISRG Root X1 cross-signed by DST ROOT CA X3 (which is expired) + #OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrg-root-x1-cross-signed.pem', __FILE__))) + ] + + cert_store = OpenSSL::X509::Store.new + cert_store.time = VERIFY_EXPIRED_TIME + root_bundle.each { |cert| cert_store.add_cert cert } + + # cross-signed cert is sent from the server : + chain = [ + #LEAF_CERTIFICATE, + # Valid Intermediate cert + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/lets-encrypt-r3.pem', __FILE__))), + # ISRG Root X1 cross-signed by DST ROOT CA X3 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrg-root-x1-cross-signed.pem', __FILE__))) + ] + + ok = cert_store.verify(LEAF_CERTIFICATE, chain) + + # pp cert_store.chain if $VERBOSE + + assert_equal ["/CN=geoip.elastic.dev", + "/C=US/O=Let's Encrypt/CN=R3", + "/C=US/O=Internet Security Research Group/CN=ISRG Root X1"], + cert_store.chain.map { |cert| cert.subject.to_s } + + assert_equal true, ok # fails in JOSSL 0.10.7 error: 10 (certificate has expired) + assert_equal 'ok', cert_store.error_string + end + + def test_cert_verify_expired0_lets_encrypt # base_line + root_bundle = [ + # Expired DST ROOT CA X3 + #EXPIRED_DST_ROOT_CA_X3, # should be fine since we do not have the expired around + # active ISRG Root X1 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrgrootx1.pem', __FILE__))), + # ISRG Root X1 cross-signed by (expired) DST ROOT CA X3 + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/isrg-root-x1-cross-signed.pem', __FILE__))) + ] + + cert_store = OpenSSL::X509::Store.new + cert_store.time = VERIFY_EXPIRED_TIME + root_bundle.each { |cert| cert_store.add_cert cert } + + chain = [ + # Intermediate cert from expired CA + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/lets-encrypt-r3-cross-signed.pem', __FILE__))), + # Valid Intermediate cert + OpenSSL::X509::Certificate.new(File.read(File.expand_path('../letsencrypt/lets-encrypt-r3.pem', __FILE__))), + ] + + ok = cert_store.verify(LEAF_CERTIFICATE, chain) + + assert ok # works in JOSSL 0.10.7 + assert_equal 'ok', cert_store.error_string + assert_equal ["/CN=geoip.elastic.dev", + "/C=US/O=Let's Encrypt/CN=R3", + "/C=US/O=Internet Security Research Group/CN=ISRG Root X1"], + cert_store.chain.map { |cert| cert.subject.to_s } + + cert_store = OpenSSL::X509::Store.new + cert_store.time = VERIFY_EXPIRED_TIME + cert_store.add_cert root_bundle[1] # only the expired one + + ok = cert_store.verify(LEAF_CERTIFICATE, chain) + + assert !ok + assert_equal 'unable to get issuer certificate', cert_store.error_string + end + + def test_getbyte + start_server(OpenSSL::SSL::VERIFY_NONE, true) { |_, 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_sync_close + start_server(OpenSSL::SSL::VERIFY_NONE, true) 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 + end diff --git a/src/test/ruby/test_asn1.rb b/src/test/ruby/test_asn1.rb index 75739553..0cc59e27 100644 --- a/src/test/ruby/test_asn1.rb +++ b/src/test/ruby/test_asn1.rb @@ -3,8 +3,212 @@ class TestASN1 < TestCase + def test_decode_x509_certificate + subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") + key = Fixtures.pkey("rsa1024") + now = Time.at(Time.now.to_i) # suppress usec + s = 0xdeadbeafdeadbeafdeadbeafdeadbeaf + exts = [ + ["basicConstraints","CA:TRUE,pathlen:1",true], + ["keyUsage","keyCertSign, cRLSign",true], + ["subjectKeyIdentifier","hash",false], + ] + dgst = OpenSSL::Digest.new('SHA256') + cert = issue_cert(subj, key, s, exts, nil, nil, digest: dgst, not_before: now, not_after: now+3600) + + + asn1 = OpenSSL::ASN1.decode(cert) + assert_equal(OpenSSL::ASN1::Sequence, asn1.class) + assert_equal(3, asn1.value.size) + tbs_cert, sig_alg, sig_val = *asn1.value + + assert_equal(OpenSSL::ASN1::Sequence, tbs_cert.class) + assert_equal(8, tbs_cert.value.size) + + version = tbs_cert.value[0] + assert_equal(:CONTEXT_SPECIFIC, version.tag_class) + assert_equal(0, version.tag) + assert_equal(1, version.value.size) + assert_equal(OpenSSL::ASN1::Integer, version.value[0].class) + assert_equal(2, version.value[0].value) + + serial = tbs_cert.value[1] + assert_equal(OpenSSL::ASN1::Integer, serial.class) + assert_equal(0xdeadbeafdeadbeafdeadbeafdeadbeaf, serial.value) + + sig = tbs_cert.value[2] + assert_equal(OpenSSL::ASN1::Sequence, sig.class) + assert_equal(2, sig.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, sig.value[0].class) + assert_equal("1.2.840.113549.1.1.11", sig.value[0].oid) + assert_equal(OpenSSL::ASN1::Null, sig.value[1].class) + + dn = tbs_cert.value[3] # issuer + assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash) + assert_equal(OpenSSL::ASN1::Sequence, dn.class) + assert_equal(3, dn.value.size) + assert_equal(OpenSSL::ASN1::Set, dn.value[0].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[1].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[2].class) + assert_equal(1, dn.value[0].value.size) + assert_equal(1, dn.value[1].value.size) + assert_equal(1, dn.value[2].value.size) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class) + assert_equal(2, dn.value[0].value[0].value.size) + assert_equal(2, dn.value[1].value[0].value.size) + assert_equal(2, dn.value[2].value[0].value.size) + oid, value = *dn.value[0].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("org", value.value) + oid, value = *dn.value[1].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("ruby-lang", value.value) + oid, value = *dn.value[2].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("2.5.4.3", oid.oid) + assert_equal(OpenSSL::ASN1::UTF8String, value.class) + assert_equal("TestCA", value.value) + + validity = tbs_cert.value[4] + assert_equal(OpenSSL::ASN1::Sequence, validity.class) + assert_equal(2, validity.value.size) + assert_equal(OpenSSL::ASN1::UTCTime, validity.value[0].class) + assert_equal(now, validity.value[0].value) + assert_equal(OpenSSL::ASN1::UTCTime, validity.value[1].class) + assert_equal(now+3600, validity.value[1].value) + + dn = tbs_cert.value[5] # subject + assert_equal(subj.hash, OpenSSL::X509::Name.new(dn).hash) + assert_equal(OpenSSL::ASN1::Sequence, dn.class) + assert_equal(3, dn.value.size) + assert_equal(OpenSSL::ASN1::Set, dn.value[0].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[1].class) + assert_equal(OpenSSL::ASN1::Set, dn.value[2].class) + assert_equal(1, dn.value[0].value.size) + assert_equal(1, dn.value[1].value.size) + assert_equal(1, dn.value[2].value.size) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[0].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[1].value[0].class) + assert_equal(OpenSSL::ASN1::Sequence, dn.value[2].value[0].class) + assert_equal(2, dn.value[0].value[0].value.size) + assert_equal(2, dn.value[1].value[0].value.size) + assert_equal(2, dn.value[2].value[0].value.size) + oid, value = *dn.value[0].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("org", value.value) + oid, value = *dn.value[1].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("0.9.2342.19200300.100.1.25", oid.oid) + assert_equal(OpenSSL::ASN1::IA5String, value.class) + assert_equal("ruby-lang", value.value) + oid, value = *dn.value[2].value[0].value + assert_equal(OpenSSL::ASN1::ObjectId, oid.class) + assert_equal("2.5.4.3", oid.oid) + assert_equal(OpenSSL::ASN1::UTF8String, value.class) + assert_equal("TestCA", value.value) + + pkey = tbs_cert.value[6] + assert_equal(OpenSSL::ASN1::Sequence, pkey.class) + assert_equal(2, pkey.value.size) + assert_equal(OpenSSL::ASN1::Sequence, pkey.value[0].class) + assert_equal(2, pkey.value[0].value.size) + assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class) + assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid) + assert_equal(OpenSSL::ASN1::BitString, pkey.value[1].class) + assert_equal(0, pkey.value[1].unused_bits) + spkey = OpenSSL::ASN1.decode(pkey.value[1].value) + assert_equal(OpenSSL::ASN1::Sequence, spkey.class) + assert_equal(2, spkey.value.size) + assert_equal(OpenSSL::ASN1::Integer, spkey.value[0].class) + assert_equal(cert.public_key.n, spkey.value[0].value) + assert_equal(OpenSSL::ASN1::Integer, spkey.value[1].class) + assert_equal(cert.public_key.e, spkey.value[1].value) + + extensions = tbs_cert.value[7] + assert_equal(:CONTEXT_SPECIFIC, extensions.tag_class) + assert_equal(3, extensions.tag) + assert_equal(1, extensions.value.size) + assert_equal(OpenSSL::ASN1::Sequence, extensions.value[0].class) + assert_equal(3, extensions.value[0].value.size) + + ext = extensions.value[0].value[0] # basicConstraints + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(3, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.19", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class) + assert_equal(true, ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class) + extv = OpenSSL::ASN1.decode(ext.value[2].value) + assert_equal(OpenSSL::ASN1::Sequence, extv.class) + assert_equal(2, extv.value.size) + assert_equal(OpenSSL::ASN1::Boolean, extv.value[0].class) + assert_equal(true, extv.value[0].value) + assert_equal(OpenSSL::ASN1::Integer, extv.value[1].class) + assert_equal(1, extv.value[1].value) + + ext = extensions.value[0].value[1] # keyUsage + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(3, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.15", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::Boolean, ext.value[1].class) + assert_equal(true, ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[2].class) + extv = OpenSSL::ASN1.decode(ext.value[2].value) + assert_equal(OpenSSL::ASN1::BitString, extv.class) + str = +"\000"; str[0] = 0b00000110.chr + assert_equal(str, extv.value) + + ext = extensions.value[0].value[2] # subjectKeyIdentifier + assert_equal(OpenSSL::ASN1::Sequence, ext.class) + assert_equal(2, ext.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, ext.value[0].class) + assert_equal("2.5.29.14", ext.value[0].oid) + assert_equal(OpenSSL::ASN1::OctetString, ext.value[1].class) + extv = OpenSSL::ASN1.decode(ext.value[1].value) + assert_equal(OpenSSL::ASN1::OctetString, extv.class) + sha1 = OpenSSL::Digest.new('SHA1') + sha1.update(pkey.value[1].value) + assert_equal(sha1.digest, extv.value) + + assert_equal(OpenSSL::ASN1::Sequence, sig_alg.class) + assert_equal(2, sig_alg.value.size) + assert_equal(OpenSSL::ASN1::ObjectId, pkey.value[0].value[0].class) + assert_equal("1.2.840.113549.1.1.1", pkey.value[0].value[0].oid) + assert_equal(OpenSSL::ASN1::Null, pkey.value[0].value[1].class) + + assert_equal(OpenSSL::ASN1::BitString, sig_val.class) + cululated_sig = key.sign(OpenSSL::Digest.new('SHA256'), tbs_cert.to_der) + # TODO: Import Issue + # Fails from import with: + # <"\x9E\x19\xE3oI\xC0\x85n$\xF4\xCE\n" + + # "\x87\xA6\xFCu\x1AQbti\xB1\xE0o\xD5\x18?}\xFAEq\xC8\xEF\x17K\xCA|d\xDEu;%\xFB\xA1\xD4\x14\x04\x837\x90E\xAC.p=\x14\xA7\x8B\xAE\xC4\xBE-\x99\xBAx\xB8\x9B+\x87\x80\e\xA1\x17{\fV\xA0\xCF\xA60b\xDFc\x06\x81\xFB\xD3:\x01\x17\x8F\xC5[\xE0m\xAB,\xD3D\xBE\xA0\xA5\x8C\x1E\xCB\x18!\xBF&\x17\xA6\xCF\x8A\xDD\xF1\xB4\x1C\x89\xD8t\xAEz\x95\xC6\xE4\x9E\xA3\xA4"> + # expected but was + # <",\xF4.\x1CH\xD5y\xFE\x05~\xB2\x05\xB7\xCB{2VwdZ\xD7\r^\x87AF\x16\x1A\xC8+U\xA1\xCA'\x1Ca\xCE}\xD2H + #assert_equal(cululated_sig, sig_val.value) + end + def test_encode_boolean - encode_decode_test(OpenSSL::ASN1::Boolean, [true, false]) + encode_decode_test1(OpenSSL::ASN1::Boolean, [true, false]) + end + + def test_end_of_content + # TODO: Import Issue + # raises OpenSSL::ASN1::ASN1Error: unexpected end-of-contents marker + #encode_decode_test B(%w{ 00 00 }), OpenSSL::ASN1::EndOfContent.new + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 00 01 00 })) + } end def test_encode_integer @@ -21,16 +225,152 @@ def test_encode_integer assert_equal i, OpenSSL::ASN1.decode(ai.to_der).value end + def test_enumerated + encode_decode_test B(%w{ 0A 01 00 }), OpenSSL::ASN1::Enumerated.new(0) + encode_decode_test B(%w{ 0A 01 48 }), OpenSSL::ASN1::Enumerated.new(72) + encode_decode_test B(%w{ 0A 02 00 80 }), OpenSSL::ASN1::Enumerated.new(128) + encode_decode_test B(%w{ 0A 09 01 00 00 00 00 00 00 00 00 }), OpenSSL::ASN1::Enumerated.new(2 ** 64) + end + + def test_encode_nested_sequence_to_der + data_sequence = ::OpenSSL::ASN1::Sequence([::OpenSSL::ASN1::Integer(0)]) + asn1 = ::OpenSSL::ASN1::Sequence(data_sequence) + assert_equal "0\x03\x02\x01\x00", asn1.to_der + end + + def test_encode_nested_set_to_der + data_set = ::OpenSSL::ASN1::Set([::OpenSSL::ASN1::Integer(0)]) + asn1 = ::OpenSSL::ASN1::Set(data_set) + assert_equal "1\x03\x02\x01\x00", asn1.to_der + 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_encode_asn1_data + ai = OpenSSL::ASN1::ASN1Data.new(i = "bla", 0, :APPLICATION) + ai2 = OpenSSL::ASN1.decode(ai.to_der) + assert_equal :APPLICATION, ai2.tag_class + assert_equal 0, ai2.tag + assert_equal i, ai2.value + + ai = OpenSSL::ASN1::ASN1Data.new(i = "bla", 4, :UNIVERSAL) + ai2 = OpenSSL::ASN1.decode(ai.to_der) + assert_equal :UNIVERSAL, ai2.tag_class + assert_equal 4, ai2.tag + assert_equal i, ai2.value + + ai = OpenSSL::ASN1::ASN1Data.new(i = ["bla"], 0, :APPLICATION) + ai2 = OpenSSL::ASN1.decode(ai.to_der) + assert_equal :APPLICATION, ai2.tag_class + assert_equal 0, ai2.tag + assert_equal "bla", ai2.value + + ai = OpenSSL::ASN1::ASN1Data.new(i = ["bla", "bla"], 0, :APPLICATION) + ai2 = OpenSSL::ASN1.decode(ai.to_der) + assert_equal :APPLICATION, ai2.tag_class + assert_equal 0, ai2.tag + assert_equal "blabla", ai2.value + + assert_raise(ArgumentError) { OpenSSL::ASN1::ASN1Data.new(1).to_der } + assert_raise("no implicit conversion of Integer into String") { OpenSSL::ASN1::ASN1Data.new(1, 0, :APPLICATION).to_der } + assert_raise("no implicit conversion of Integer into String") { OpenSSL::ASN1::ASN1Data.new(1, 0, :CONTEXT_SPECIFIC).to_der } + end + def test_encode_nil #Primitives raise TypeError, Constructives NoMethodError assert_raise(TypeError) { OpenSSL::ASN1::Integer.new(nil).to_der } assert_raise(TypeError) { OpenSSL::ASN1::Boolean.new(nil).to_der } + end - # NOTE: MRI does not raise - assert_raise(NoMethodError) { OpenSSL::ASN1::Set.new(nil).to_der } - # NOTE: MRI does not raise - assert_raise(NoMethodError) { OpenSSL::ASN1::Sequence.new(nil).to_der } + def test_encode_data_integer + data = OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::Integer.new(90)], 1, :CONTEXT_SPECIFIC) + der = data.to_der + assert_equal "\xA1\x03\x02\x01Z", der + + dec = OpenSSL::ASN1.decode(der) + # #>]> + assert_equal OpenSSL::ASN1::ASN1Data, dec.class + assert_equal :CONTEXT_SPECIFIC, dec.tag_class + assert_equal 1, dec.tag + + assert_equal Array, dec.value.class + assert_equal 1, dec.value.size + int = dec.value[0] + assert_equal OpenSSL::ASN1::Integer, int.class + assert_equal 2, int.tag + assert_equal :UNIVERSAL, int.tag_class + assert_equal OpenSSL::BN.new(90), int.value + end + + def test_object_identifier + encode_decode_test B(%w{ 06 01 00 }), OpenSSL::ASN1::ObjectId.new("0.0".b) + 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 + # TODO: Import Issue + # Fails with: expected but was ) + #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 })) + #} + # expected but was ) + #assert_raise(OpenSSL::ASN1::ASN1Error) { OpenSSL::ASN1::ObjectId.new("3.0".b).to_der } + # exception was expected but none was thrown. + #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 + + 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| + # TODO: Import Issue + # None of these are equivalent to each other + #aki.each do |b| + # assert a == b + #end + + ski.each do |b| + refute a == b + end + end + + # TODO: Import Issue + # exception was expected but none was thrown. + #assert_raise(TypeError) { + # OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil + #} + + oid = OpenSSL::ASN1::ObjectId.new("2.5.29.14") + assert_equal true, oid == OpenSSL::ASN1::ObjectId.new("2.5.29.14") + assert_equal false, oid == OpenSSL::ASN1::ObjectId.new("2.5.29.35") end def test_instantiate @@ -50,9 +390,7 @@ def test_constants assert_equal universal_tag_name, OpenSSL::ASN1::UNIVERSAL_TAG_NAME end - require 'pp' - - def _test_parse_infinite_length_sequence # borrowed from Krypt + def _test_parse_infinite_length_sequence; require 'pp' # borrowed from Krypt raw = [%w{30 80 04 01 01 02 01 01 00 00}.join("")].pack("H*") pp asn1 = OpenSSL::ASN1.decode(raw) @@ -71,6 +409,165 @@ def _test_parse_infinite_length_sequence # borrowed from Krypt assert_equal(raw, asn1.to_der) end + def test_simple_to_der + assert_equal "0\x00", OpenSSL::ASN1::Sequence.new(nil).to_der + assert_equal "1\x00", OpenSSL::ASN1::Set.new(nil).to_der + 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 + # TODO: Import Issue + # exception was expected but none was thrown. + #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 + end + + def test_set + encode_decode_test B(%w{ 31 00 }), OpenSSL::ASN1::Set.new([]) + # TODO: Import Issue + # <"1\a\x05\x000\x00\x04\x01\x00"> expected but was <"1\a\x04\x01\x00\x05\x000\x00"> + #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)) + begin + # possible range of UTCTime is 1969-2068 currently + encode_decode_test B(%w{ 17 0D }) + "690908234339Z".b, + OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 39)) + rescue OpenSSL::ASN1::ASN1Error + pend "No negative time_t support?" + end + # not implemented + # 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")) + # 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)) + # not implemented + # 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")) + # decode_test B(%w{ 18 0D }) + "2016120819.5Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) + # decode_test B(%w{ 18 0D }) + "2016120819,5Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 30, 0)) + # decode_test B(%w{ 18 0F }) + "201612081934.5Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 30)) + # decode_test B(%w{ 18 11 }) + "20161208193439.5Z".b, + # OpenSSL::ASN1::GeneralizedTime.new(Time.utc(2016, 12, 8, 19, 34, 39.5)) + # assert_raise(OpenSSL::ASN1::ASN1Error) { + # OpenSSL::ASN1.decode(B(%w{ 18 0D }) + "201612081934Y".b) + # } + 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 + + # + # MRI seems to have some different tests for similar things (constructive + # vs constructed) There may be some duplicated test cases within these but + # for completeness' sake I'm going to include all of MRI's tests below + # + + 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_test B(%w{ 21 80 04 02 AB CD 00 00 }), obj + # TODO: BC doesn't support decoding indef constructive asn1s from unsupported tag types. + # 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_constructive oct = OpenSSL::ASN1::OctetString.new("") assert_equal "\x04\x00", oct.to_der @@ -103,6 +600,203 @@ def test_constructive assert_equal "0\x800\x80\x02\x01\x01\x00\x00\x00\x00", outer.to_der 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) + # <:APPLICATION> expected but was <:UNIVERSAL> + #assert_equal :APPLICATION, decoded.tag_class + # <1> expected but was <2> + #assert_equal 1, decoded.tag + # <"\x01"> expected but was <#> + #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) + # <"\x01\x01\x01"> expected but was <"\x02\x01\x01"> + #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) + # TODO: Import Issue + # RuntimeError: No message available + #encode_test B(%w{ A2 07 30 05 13 03 61 62 63 }), seq + #seq2 = OpenSSL::ASN1::Sequence.new(content, 3, :EXPLICIT, :APPLICATION) + # RuntimeError: No message available + #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 + # RuntimeError: No message available + #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) + # TODO: Import Issue + # <"\xA1\x02\x05\x00"> expected but was <"0\x02\x05\x00"> + #encode_test B(%w{ A1 02 05 00 }), seq + #seq2 = OpenSSL::ASN1::Sequence.new(content, 1, :IMPLICIT, :APPLICATION) + # <"a\x02\x05\x00"> expected but was <"0\x02\x05\x00"> + #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 + # <"\xA1\x80\x05\x00\x00\x00"> expected but was <"0\x80\x05\x00\x00\x00"> + #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) + # <"!\x00"> expected but was <"0\x00"> + #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) + # TODO: Import Issues + # OpenSSL::ASN1::ASN1Error: Constructive shall only be used with indefinite length + #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 + # Java::JavaLang::UnsupportedOperationException: + # #, + # #], @tag_class=:CONTEXT_SPECIFIC, @tagging=:IMPLICIT, @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 }) + # TODO: Implict Issue + # Java::JavaLang::UnsupportedOperationException + # org.jruby.ext.openssl.ASN1$Constructive$InternalEncodable.toASN1Primitive(ASN1.java:1961) + # org.jruby.ext.openssl.ASN1$Constructive.octetStringToDER(ASN1.java:1904) + # org.jruby.ext.openssl.ASN1$Constructive.toDER(ASN1.java:1873) + # org.jruby.ext.openssl.ASN1$ASN1Data.to_der(ASN1.java:1414) + #assert_equal(raw, cons.to_der) + # <"$\x80$\x80\x04\x01\x01\x00\x00$\x80\x04\x01\x02\x00\x00\x04\x01\x03\x00\x00"> expected but was <"$\x80\x04\x03\x01\x02\x03\x00\x00"> + #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) + # TODO: Import Issue + # expected but was + #assert_universal(OpenSSL::ASN1::OCTET_STRING, asn1) + assert_equal(true, asn1.indefinite_length) + # <3> expected but was <2> + #assert_equal(3, asn1.value.size) + nested1 = asn1.value[0] + # expected but was + #assert_equal(OpenSSL::ASN1::Constructive, nested1.class) + assert_universal(OpenSSL::ASN1::OCTET_STRING, nested1) + # expected but was + #assert_equal(true, nested1.indefinite_length) + # <1> expected but was <3> + #assert_equal(1, nested1.value.size) + #oct1 = nested1.value[0] + # NoMethodError: undefined method `tag' for "\x01":String + # Did you mean? tap + # src/test/ruby/test_asn1.rb:1286:in `assert_universal' + #assert_universal(OpenSSL::ASN1::OCTET_STRING, oct1) + # NoMethodError: undefined method `indefinite_length' for "\x01":String + #assert_equal(false, oct1.indefinite_length) + #nested2 = asn1.value[1] + # expected but was + #assert_equal(OpenSSL::ASN1::Constructive, nested2.class) + # <4> expected but was <0> + #assert_universal(OpenSSL::ASN1::OCTET_STRING, nested2) + # expected but was + #assert_equal(true, nested2.indefinite_length) + # <1> expected but was <0> + #assert_equal(1, nested2.value.size) + #oct2 = nested2.value[0] + # NoMethodError: undefined method `tag' for nil:NilClass + #assert_universal(OpenSSL::ASN1::OCTET_STRING, oct2) + # NoMethodError: undefined method `indefinite_length' for nil:NilClass + #assert_equal(false, oct2.indefinite_length) + #oct3 = asn1.value[2] + # NoMethodError: undefined method `tag' for nil:NilClass + #assert_universal(OpenSSL::ASN1::OCTET_STRING, oct3) + # NoMethodError: undefined method `indefinite_length' for nil:NilClass + #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 = [] + # exception was expected but none was thrown. + #assert_raise(OpenSSL::ASN1::ASN1Error) { + # OpenSSL::ASN1.traverse(raw) { |x| ret << x } + #} + # <2> expected but was <0> + #assert_equal 2, ret.size + # NoMethodError: undefined method `[]' for nil:NilClass + #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 = [] + # exception was expected but none was thrown. + #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_nesting seq = OpenSSL::ASN1::Sequence.new([ OpenSSL::ASN1::ObjectId.new('DC'), @@ -167,6 +861,14 @@ def test_constructive_nesting assert_equal expected, name.to_der end + def test_sequence_convert_to_array + data_sequence = ::OpenSSL::ASN1::Sequence([::OpenSSL::ASN1::Integer(0)]) + asn1 = ::OpenSSL::ASN1::Sequence(data_sequence) + assert_equal "0\x03\x02\x01\x00" , asn1.to_der + + assert_raise(TypeError) { ::OpenSSL::ASN1::Sequence(::OpenSSL::ASN1::Integer(0)).to_der } + end + def test_raw_constructive eoc = OpenSSL::ASN1::EndOfContent.new #puts "eoc: #{eoc.inspect}" @@ -188,8 +890,7 @@ def test_raw_constructive nil, :UNIVERSAL ) assert_equal false, inf_octets.infinite_length - # The real value of inf_octets is "\x01\x02", i.e. the concatenation - # of partial1 and partial2 + # The real value of inf_octets is "\x01\x02", i.e. the concatenation of partial1 and partial2 inf_octets.infinite_length = true assert_equal true, inf_octets.infinite_length @@ -253,8 +954,7 @@ def test_decode ["subjectKeyIdentifier","hash",false], ] dgst = OpenSSL::Digest::SHA1.new - cert = issue_cert( # OpenSSL::TestUtils.issue_cert - subj, key, s, now, now+3600, exts, nil, nil, dgst) + cert = issue_cert(subj, key, s, exts, nil, nil, not_before: now, not_after: now + 3600, digest: dgst) asn1 = OpenSSL::ASN1.decode(cert) assert_equal(OpenSSL::ASN1::Sequence, asn1.class) @@ -451,10 +1151,42 @@ def test_decode assert OpenSSL::X509::Certificate.new( cert_der ).verify key # running the same in MRI also fails - #calulated_sig = key.sign(OpenSSL::Digest::SHA1.new, cert_der) + calulated_sig = key.sign(OpenSSL::Digest::SHA1.new, cert_der) #assert_equal calulated_sig, sig_val.value end + # This is from the upstream MRI tests + def test_bitstring + encode_decode_test B(%w{ 03 01 00 }), OpenSSL::ASN1::BitString.new(B(%w{})) + encode_decode_test B(%w{ 03 02 00 01 }), OpenSSL::ASN1::BitString.new(B(%w{ 01 })) + + obj = OpenSSL::ASN1::BitString.new(B(%w{ F0 })) + obj.unused_bits = 4 + encode_decode_test B(%w{ 03 02 04 F0 }), obj + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 03 00 })) + } + assert_raise(OpenSSL::ASN1::ASN1Error) { + OpenSSL::ASN1.decode(B(%w{ 03 03 08 FF 00 })) + } + + assert_raise(OpenSSL::ASN1::ASN1Error) { + obj = OpenSSL::ASN1::BitString.new(B(%w{ FF FF })) + obj.unused_bits = 8 + obj.to_der + } + end + + def test_bit_string_unused_length + asn = "\x00\x04".b + asn.prepend "\x03#{asn.bytesize.chr}" + bs = OpenSSL::ASN1.decode(asn) + assert_equal asn, bs.to_der + + bs.unused_bits = 6 + assert_equal "\x03\x02\x06\x00", bs.to_der + end + def test_bit_string_infinite_length begin content = [ OpenSSL::ASN1::BitString.new("\x01"), OpenSSL::ASN1::EndOfContent.new() ] @@ -463,11 +1195,37 @@ def test_bit_string_infinite_length expected = %w{ 23 80 03 02 00 01 00 00 } raw = [expected.join('')].pack('H*') assert_equal raw, cons.to_der - # TODO for now we can no decode our own sh*t : + # TODO for now we can not decode our own sh*t : #assert_equal raw, OpenSSL::ASN1.decode(raw).to_der end end + def test_string_basic + test = -> (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_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 + def test_decode_all expected = %w{ 02 01 01 02 01 02 02 01 03 } raw = [expected.join('')].pack('H*') @@ -498,22 +1256,81 @@ def test_decode_application_specific assert_equal OpenSSL::BN, asn1_data.value[0].value.class assert_equal OpenSSL::ASN1::OctetString, asn1_data.value[1].class assert_equal 'o=Telstra', asn1_data.value[1].value -# assert_equal OpenSSL::ASN1::ASN1Data, asn1_data.value[2].class -# assert_equal :CONTEXT_SPECIFIC, asn1_data.value[2].tag_class -# assert_equal 'ess', asn1_data.value[2].value + assert_equal OpenSSL::ASN1::ASN1Data, asn1_data.value[2].class + assert_equal :CONTEXT_SPECIFIC, asn1_data.value[2].tag_class + assert_equal 'ess', asn1_data.value[2].value # assert_equal raw, asn1.to_der end + + def test_encode_der_integer_wrapped + asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(42), OpenSSL::ASN1::Integer(84) ]) + + der = "0\x06\x02\x01*\x02\x01T" + assert_equal der, asn1.to_der + + asn1 = OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::Integer(OpenSSL::BN.new(42)), OpenSSL::ASN1::Integer(OpenSSL::BN.new(84)) ]) + + der = "0\x06\x02\x01*\x02\x01T" + assert_equal der, asn1.to_der + + i = OpenSSL::ASN1::Integer(OpenSSL::BN.new('1234567890')) + assert_equal 1234567890, i.value.to_i + + i = OpenSSL::ASN1::Integer('12345678901234567890') + assert_equal 12345678901234567890, i.value.to_i + 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, inf_len=false) assert_equal(tag, asn1.tag) assert_equal(:UNIVERSAL, asn1.tag_class) assert_equal(inf_len, asn1.infinite_length) end - def encode_decode_test(type, values) + def encode_decode_test1(type, values) values.each do |v| assert_equal(v, OpenSSL::ASN1.decode(type.new(v).to_der).value) end diff --git a/src/test/ruby/test_bn.rb b/src/test/ruby/test_bn.rb index 74cd02ee..efa787ca 100644 --- a/src/test/ruby/test_bn.rb +++ b/src/test/ruby/test_bn.rb @@ -2,6 +2,85 @@ class TestBN < 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_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_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_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_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_new bn = OpenSSL::BN.new('0') unless defined? JRUBY_VERSION assert_equal ( bn || OpenSSL::BN.new(0) ).to_s, '0' @@ -78,7 +157,9 @@ def test_comparison def test_to_java assert_equal java.lang.Integer.new(42), OpenSSL::BN.new('42').to_java(:int) - assert_equal java.math.BigInteger.valueOf(24), OpenSSL::BN.new('24').to_java + assert_equal java.math.BigInteger.valueOf(24), val = OpenSSL::BN.new('24').to_java + assert_equal java.math.BigInteger, val.class + assert_equal java.math.BigInteger.valueOf(24), val = OpenSSL::BN.new('24').to_java(java.lang.Number) end if defined? JRUBY_VERSION def test_new_str diff --git a/src/test/ruby/test_cipher.rb b/src/test/ruby/test_cipher.rb index e08cd33a..1ef7c4e6 100644 --- a/src/test/ruby/test_cipher.rb +++ b/src/test/ruby/test_cipher.rb @@ -3,11 +3,6 @@ class TestCipher < TestCase - def setup - super - self.class.disable_security_restrictions! - end - def test_cipher_new OpenSSL::Cipher.new 'AES-256-CBC' # NOTE: MRI 1.9.3 raises RuntimeError : @@ -363,17 +358,17 @@ def test_aes_128_gcm #assert_equal "", cipher.final end - def test_aes_gcm + def test_aes_gcm_custom ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'].each do |algo| pt = "You should all use Authenticated Encryption!" - cipher, key, iv = new_encryptor(algo) + cipher, key, iv = new_random_encryptor(algo) cipher.auth_data = "aad" ct = cipher.update(pt) + cipher.final tag = cipher.auth_tag assert_equal(16, tag.size) - decipher = new_decryptor(algo, key, iv) + decipher = new_decryptor(algo, key: key, iv: iv) decipher.auth_tag = tag decipher.auth_data = "aad" @@ -381,23 +376,21 @@ def test_aes_gcm end end - def new_encryptor(algo) + 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 new_random_encryptor(algo) cipher = OpenSSL::Cipher.new(algo) cipher.encrypt key = cipher.random_key iv = cipher.random_iv [cipher, key, iv] end - private :new_encryptor - - def new_decryptor(algo, key, iv) - OpenSSL::Cipher.new(algo).tap do |cipher| - cipher.decrypt - cipher.key = key - cipher.iv = iv - end - end - private :new_decryptor + private :new_random_encryptor def test_aes_128_gcm_with_auth_tag cipher = OpenSSL::Cipher.new('aes-128-gcm') @@ -477,25 +470,97 @@ def test_encrypt_aes_cfb_20_incompatibility def test_encrypt_aes_256_cbc_modifies_buffer cipher = OpenSSL::Cipher.new("AES-256-CBC") - cipher.key = "a" * 32 cipher.encrypt + cipher.key = "a" * 32 buffer = '' actual = cipher.update('bar' * 10, buffer) - if jruby? - expected = "\xE6\xD3Y\fc\xEE\xBA\xB2*\x0Fr\xD1\xC2b\x03\xD0" - else - expected = "8\xA7\xBE\xB1\xAE\x88j\xCB\xA3\xE9j\x00\xD2W_\x91" - end + expected = "\xE6\xD3Y\fc\xEE\xBA\xB2*\x0Fr\xD1\xC2b\x03\xD0" assert_equal actual, expected assert_equal buffer, expected end def test_encrypt_aes_256_cbc_invalid_buffer cipher = OpenSSL::Cipher.new("AES-256-CBC") - cipher.key = "a" * 32 cipher.encrypt + cipher.key = "a" * 32 buffer = Object.new assert_raise(TypeError) { cipher.update('bar' * 10, buffer) } end + def test_aes_gcm + # GCM spec Appendix B Test Case 4 + key = ["feffe9928665731c6d6a8f9467308308"].pack("H*") + iv = ["cafebabefacedbaddecaf888"].pack("H*") + aad = ["feedfacedeadbeeffeedfacedeadbeef" \ + "abaddad2"].pack("H*") + pt = ["d9313225f88406e5a55909c5aff5269a" \ + "86a7a9531534f7da2e4c303d8a318a72" \ + "1c3c0c95956809532fcf0e2449a6b525" \ + "b16aedf5aa0de657ba637b39"].pack("H*") + ct = ["42831ec2217774244b7221b784d0d49c" \ + "e3aa212f2c02a4e035c17e2329aca12e" \ + "21d514b25466931c7d8f6a5aac84aa05" \ + "1ba30b396a0aac973d58e091"].pack("H*") + tag = ["5bc94fbc3221a5db94fae95ae7121a47"].pack("H*") + + cipher = new_encryptor("aes-128-gcm", key: key, iv: iv, auth_data: aad) + # TODO JOpenSSL should raise + # assert_raise(OpenSSL::Cipher::CipherError, 'unable to set authentication tag length: failed to get parameter') do + # cipher.auth_tag_len = 16 + # end + 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) + # TODO JOpenSSL should raise + # assert_raise(OpenSSL::Cipher::CipherError, 'unable to set authentication tag length: failed to get parameter') do + # cipher.auth_tag_len = 16 + # end + 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) + assert_equal tag, cipher.auth_tag + + # NOTE: MRI seems to just ignore the invalid tag?! + # cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag[0, 8], auth_data: aad) + # assert_equal pt, cipher.update(ct) << cipher.final + + # wrong tag is rejected + tag2 = tag.dup + tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) + cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag2, auth_data: aad) + cipher.update(ct) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + + # wrong aad is rejected + aad2 = aad[0..-2] << aad[-1].succ + cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad2) + cipher.update(ct) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + + # wrong ciphertext is rejected + ct2 = ct[0..-2] << ct[-1].succ + cipher = new_decryptor("aes-128-gcm", key: key, iv: iv, auth_tag: tag, auth_data: aad) + cipher.update(ct2) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.final } + end + + 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 diff --git a/src/test/ruby/test_digest.rb b/src/test/ruby/test_digest.rb index 820701aa..22a6f46e 100644 --- a/src/test/ruby/test_digest.rb +++ b/src/test/ruby/test_digest.rb @@ -116,4 +116,12 @@ def test_098_features assert_equal(sha512_a, encode16(OpenSSL::Digest::SHA512.digest("a"))) end + def test_net_ssh_like_loading + require 'openssl' + require 'openssl/digest' + # shout not raise TypeError: superclass mismatch for class Digest + assert OpenSSL::Digest.is_a?(Class) + assert OpenSSL::Digest("MD5") + end + end \ No newline at end of file diff --git a/src/test/ruby/test_helper.rb b/src/test/ruby/test_helper.rb index e42818d8..1e093c34 100644 --- a/src/test/ruby/test_helper.rb +++ b/src/test/ruby/test_helper.rb @@ -1,5 +1,3 @@ -require 'rubygems' unless defined? Gem - require 'java' if defined? JRUBY_VERSION if bc_version = ENV['BC_VERSION'] # && respond_to?(:require_jar) @@ -65,7 +63,7 @@ def self.plugin_output_init(options) puts "#{__FILE__} using #{TestCase}" if $VERBOSE || defined?(REPORT_PATH) -TestCase.class_eval do +class TestCase def setup; require 'openssl' end @@ -103,32 +101,6 @@ def assert_not_same(expected, actual) end end - def self.disable_security_restrictions!; @@security_restrictions = nil end # do nothing on MRI - - @@security_restrictions = '' - - def self.disable_security_restrictions! - debug = OpenSSL.debug - begin - OpenSSL.debug = true - #org.jruby.ext.openssl.util.CryptoSecurity.unrestrictSecurity - #org.jruby.ext.openssl.util.CryptoSecurity.setAllPermissionPolicy - @@security_restrictions = OpenSSL.send :_disable_security_restrictions! - ensure - OpenSSL.debug = debug - end - end if defined? JRUBY_VERSION - - def self.disable_security_restrictions - disable_security_restrictions! if @@security_restrictions.eql?('') - end - - def self.security_restrictions? - disable_security_restrictions; return @@security_restrictions - end - - def self.java6?; java_version.last.to_i == 6 end - def self.java7?; java_version.last.to_i == 7 end def self.java8?; java_version.last.to_i == 8 end def self.java_version @@ -143,17 +115,7 @@ def jruby?; self.class.jruby? end def debug(msg); puts msg if $VERBOSE end - def issue_cert(*args) - # def issue_cert(dn, key, serial, not_before, not_after, extensions, issuer, issuer_key, digest) - # def issue_cert(dn, key, serial, extensions, issuer, issuer_key, - # not_before: nil, not_after: nil, digest: "sha256") - if args.length == 9 - dn, key, serial, not_before, not_after, extensions, issuer, issuer_key, digest = *args - else - dn, key, serial, extensions, issuer, issuer_key, opts = *args - opts ||= {} - not_before, not_after, digest = opts[:not_before], opts[:not_after], opts[:digest] || "sha256" - end + def issue_cert(dn, key, serial, extensions, issuer, issuer_key, not_before: nil, not_after: nil, digest: 'sha256') cert = OpenSSL::X509::Certificate.new issuer = cert unless issuer issuer_key = key unless issuer_key @@ -175,8 +137,7 @@ def issue_cert(*args) cert end - def issue_crl(revoke_info, serial, lastup, nextup, extensions, - issuer, issuer_key, digest) + 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 @@ -212,6 +173,25 @@ def get_subject_key_id(cert) OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase end + module Fixtures + module_function + + def pkey(name) + OpenSSL::PKey.read(read_file("pkey", name)) + end + + def pkey_dh(name) + # DH parameters can be read by OpenSSL::PKey.read atm + OpenSSL::PKey::DH.new(read_file("pkey", name)) + end + + @@__fixtures_cache = {} + + def read_file(category, name) + @@__fixtures_cache[ [category, name] ] ||= + File.read(File.join(File.dirname(__FILE__), "fixtures", category, name)) + end + end end begin diff --git a/src/test/ruby/test_openssl.rb b/src/test/ruby/test_openssl.rb index 78603c29..cda69766 100644 --- a/src/test/ruby/test_openssl.rb +++ b/src/test/ruby/test_openssl.rb @@ -13,23 +13,23 @@ def test_gem_version end if ENV['BC_VERSION'] def test_version - if RUBY_VERSION.index('1.8') - assert_equal '1.0.0', OpenSSL::VERSION - else - assert_equal '1.1.0', OpenSSL::VERSION - end + assert_equal String, OpenSSL::VERSION.class + assert /\d\.\d\.\d/ =~ OpenSSL::VERSION, OpenSSL::VERSION + assert OpenSSL::OPENSSL_VERSION.index('OpenSSL') if defined? JRUBY_VERSION assert_equal 0, OpenSSL::OPENSSL_VERSION.index('JRuby-OpenSSL ') end assert OpenSSL::OPENSSL_VERSION_NUMBER - if RUBY_VERSION > '2.0' - # MRI 2.3 openssl/utils.rb does this (and we shall pass) : - assert defined?(OpenSSL::OPENSSL_LIBRARY_VERSION) - assert /\AOpenSSL +0\./ !~ OpenSSL::OPENSSL_LIBRARY_VERSION - #puts "OpenSSL::OPENSSL_LIBRARY_VERSION = #{OpenSSL::OPENSSL_LIBRARY_VERSION.inspect}" - end + # MRI 2.3 openssl/utils.rb does this (and we shall pass) : + assert defined?(OpenSSL::OPENSSL_LIBRARY_VERSION) + assert /\AOpenSSL +0\./ !~ OpenSSL::OPENSSL_LIBRARY_VERSION + end + + # some gems check this - better to be conservative until 3.0.0 APIs are fully supported + def test_version_lt_3_0_0 + assert OpenSSL::OPENSSL_VERSION_NUMBER < 3 * 0x10000000 end def test_debug @@ -45,6 +45,7 @@ def test_stubs OpenSSL.deprecated_warning_flag OpenSSL.check_func(:func, :header) OpenSSL.fips_mode = false + assert !OpenSSL.fips_mode end def test_Digest diff --git a/src/test/ruby/test_pkey.rb b/src/test/ruby/test_pkey.rb new file mode 100644 index 00000000..2a0f0e12 --- /dev/null +++ b/src/test/ruby/test_pkey.rb @@ -0,0 +1,115 @@ +# coding: US-ASCII +require File.expand_path('test_helper', File.dirname(__FILE__)) + +class TestPKey < TestCase + + KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArTlm5TxJp3WHMNmWIfo/\nWvkyhJCXc1S78Y9B8lSXxXnkRqX8Twxu5EkdUP0TwgD5gp0TGy7UPm/SgWlQOcqX\nqtdOWq/Hk29Ve9z6k6wTmst7NTefmm/7OqkeYmBhfhoECLCKBADM8ctjoqD63R0e\n3bUW2knq6vCS5YMmD76/5UoU647BzB9CjgDzjuTKEbXL5AvcO5wWDgHSp7CA+2t4\nIFQvQMrPso5mvm2hNvD19vI0VjiY21rKgkJQAXSrLgkJg/fTL2wQiz10d2GnYsmx\nDeJCiBMwC+cmRW2eWePqaCPaWJwr92KsIiry+LgyGb3y01SUVV8kQgQXazutHqfu\ncQIDAQAB\n-----END PUBLIC KEY-----\n" + + def test_pkey_read + pkey = OpenSSL::PKey.read(KEY) + assert_same OpenSSL::PKey::RSA, pkey.class + assert_true pkey.public? + assert_false pkey.private? + assert_equal OpenSSL::PKey::RSA.new(KEY).n, pkey.n + assert_equal OpenSSL::PKey::RSA.new(KEY).e, pkey.e + end + + def test_read_files + custom_fixtures_path = File.expand_path('fixtures/pkey/custom', File.dirname(__FILE__)) + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec256-private-v2.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.private_key? + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec256k-private.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.private_key? + assert_equal 'secp256k1', key.group.curve_name + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec256k-public.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.public_key? + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec256-public-v2.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.public_key? + assert_equal 'prime256v1', key.group.curve_name + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec512-private.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.private_key? + assert_equal 'secp521r1', key.group.curve_name + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'ec512-public.pem'))) + assert_equal OpenSSL::PKey::EC, key.class + assert key.public_key? + assert_equal 'secp521r1', key.group.curve_name + + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'rsa-2048-private.pem'))) + assert_equal OpenSSL::PKey::RSA, key.class + assert key.private? + key = OpenSSL::PKey.read(File.read(File.join(custom_fixtures_path, 'rsa-2048-public.pem'))) + assert_equal OpenSSL::PKey::RSA, key.class + assert key.public? + end + + def test_pkey_read_pkcs8_and_check_with_cert + pkey = File.expand_path('pkey-pkcs8.pem', File.dirname(__FILE__)) + pkey = OpenSSL::PKey.read(File.read(pkey), nil) + + assert_true pkey.private? + assert_true pkey.public? + assert pkey.public_key.to_s + + cert = File.expand_path('pkey-cert.pem', File.dirname(__FILE__)) + cert = OpenSSL::X509::Certificate.new(File.read(cert)) + + assert_true cert.check_private_key(pkey) + end + + def test_pkey_pem_file_error + begin + ret = OpenSSL::PKey.read('not a PEM file') + fail "expected OpenSSL::PKey.read to raise (got: #{ret.inspect})" + rescue OpenSSL::PKey::PKeyError => e + assert_equal 'Could not parse PKey: unsupported', e.message + end + + begin + ret = OpenSSL::PKey::RSA.new('not a PEM file') + fail "expected OpenSSL::PKey::RSA.new to raise (got: #{ret.inspect})" + rescue OpenSSL::PKey::RSAError + assert true + end + end + + def test_pkey_dh + dh = OpenSSL::PKey::DH.new + assert_equal nil, dh.p + assert_equal nil, dh.priv_key + + # OpenSSL::PKey::PKeyError: dh#set_pqg= is incompatible with OpenSSL 3.0 + if defined? JRUBY_VERSION + dh.set_pqg(1_000_000, nil, 10) + assert_equal 1_000_000, dh.p + assert_equal 10, dh.g + end + assert_equal nil, dh.q + end + + def test_to_java + pkey = OpenSSL::PKey.read(KEY) + assert_kind_of java.security.PublicKey, pkey.to_java + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.PublicKey) + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.interfaces.RSAPublicKey) + assert_kind_of java.security.PublicKey, pkey.to_java(java.security.Key) + pub_key = pkey.to_java(java.security.PublicKey) + if pub_key.is_a? org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey + assert_kind_of java.security.PublicKey, pkey.to_java(org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey) + end + assert_raise_kind_of(TypeError) { pkey.to_java(java.security.interfaces.ECPublicKey) } + # NOTE: won't fail as it's a marker that is neither a PublicKey or PrivateKey (also does not sub-class Key) + #assert_raise_kind_of(TypeError) { pkey.to_java(java.security.interfaces.ECKey) } + end if defined?(JRUBY_VERSION) + +end diff --git a/src/test/ruby/test_security_helper.rb b/src/test/ruby/test_security_helper.rb index 8139f247..7d63dd59 100644 --- a/src/test/ruby/test_security_helper.rb +++ b/src/test/ruby/test_security_helper.rb @@ -17,11 +17,15 @@ def test_cert_factory_provider_leak # GH-94 assert_equal 'BC', factory2.provider.name # assert_same factory1.getProvider, factory2.getProvider - java.security.cert.CertificateFactory.class_eval do - field_reader :certFacSpi - end + begin + java.security.cert.CertificateFactory.class_eval do + field_reader :certFacSpi + end - spi1 = factory1.certFacSpi; spi2 = factory2.certFacSpi + spi1 = factory1.certFacSpi; spi2 = factory2.certFacSpi + rescue SecurityError => e + return skip "#{__method__} probably needs --add-opens (#{e.message})" + end if spi1.is_a? org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory.class_eval do diff --git a/src/test/ruby/x509/SETUP.txt b/src/test/ruby/x509/SETUP.txt index 761f2b4b..5f7b2c7b 100644 --- a/src/test/ruby/x509/SETUP.txt +++ b/src/test/ruby/x509/SETUP.txt @@ -5,6 +5,16 @@ $ /usr/lib/ssl/misc/CA.sh -newreq $ /usr/lib/ssl/misc/CA.sh -sign Signed certificate is in newcert.pem -$ keytool -import -file demoCA/cacert.pem -alias demoCA -keystore javastore.ts -storepass javastore +$ keytool -importcert --file demoCA/cacert.pem -alias demoCA -keystore javastore.ts -storetype jks -storepass keystore Trust this certificate? [no]: y -Certificate was added to keystore \ No newline at end of file +Certificate was added to keystore + +$ keytool -list -keystore javastore.ts +Enter keystore password: +Keystore type: JKS +Keystore provider: SUN + +Your keystore contains 1 entry + +democa, Apr 20, 2024, trustedCertEntry, +Certificate fingerprint (SHA-256): ... diff --git a/src/test/ruby/x509/ca.crt b/src/test/ruby/x509/ca.crt index a2d739b1..e83dd63a 100644 --- a/src/test/ruby/x509/ca.crt +++ b/src/test/ruby/x509/ca.crt @@ -1,22 +1,80 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 7f:e0:e8:27:56:77:9b:da:39:df:f3:ae:e1:69:16:de:98:4f:fd:24 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Validity + Not Before: Apr 20 11:09:21 2024 GMT + Not After : Apr 20 11:09:21 2027 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:a0:8b:ba:ec:b2:e2:fc:47:b9:a8:dd:59:a9:47: + 49:37:0a:b0:61:c5:ee:ee:73:bc:f6:ad:9a:cc:f2: + e3:d9:82:86:a7:a9:ef:5b:9d:2e:b9:42:18:c6:46: + 01:b9:3d:15:0c:89:7b:c2:7a:bc:17:de:54:43:4a: + 89:55:53:a1:cf:7c:5b:7a:d2:c7:45:fe:da:da:d3: + a6:c5:24:55:d7:69:8b:85:db:d1:bf:47:43:45:bb: + b4:74:48:50:91:99:81:84:b5:d6:35:d4:66:c6:d0: + 69:b4:7b:8e:f8:47:f4:f7:47:a8:a5:01:87:40:d4: + 41:57:4e:93:13:0a:41:22:14:2d:e3:dc:ac:00:70: + 2d:d1:cf:08:28:d9:7b:64:d3:dd:48:ad:ce:75:57: + 90:3f:9d:c3:d3:8c:d3:80:44:96:4e:71:71:7c:db: + f7:d1:1f:3a:09:2d:13:4c:de:c7:19:5c:c1:b6:cb: + ca:91:f8:d6:7e:22:32:aa:d4:51:5d:69:6a:df:34: + 81:99:8c:fd:0e:22:30:17:89:20:15:ff:b5:00:a9: + 49:aa:f3:d5:65:e5:c8:9d:bf:c7:8f:fa:7f:56:77: + e0:5d:b4:86:a6:d7:70:d6:f6:76:8b:fe:88:84:2b: + 2b:28:81:27:59:45:08:3b:39:52:d0:df:00:9d:b8: + 54:c7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 + X509v3 Authority Key Identifier: + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 28:9d:22:51:f2:3a:0b:48:0c:8c:fa:8b:be:a4:3b:5d:dc:33: + d9:07:f0:f8:1a:cb:99:15:59:a1:87:0c:5c:8e:de:a8:96:57: + 8c:50:c2:ba:ef:fd:c4:03:0b:d8:6d:9e:3a:02:9c:99:43:2e: + b5:bf:9b:d6:81:1a:40:b1:61:50:0b:3f:da:84:85:e2:55:c7: + a6:88:d9:ed:b8:df:b6:33:03:3c:47:e3:f8:ac:df:cc:ab:2a: + 7a:a0:bf:73:6c:25:97:a4:7d:60:1d:24:6c:31:d8:b6:98:f1: + 8a:2e:3c:f0:7e:d1:fa:65:0a:6a:64:e3:bd:3f:ad:bb:8e:9d: + 02:b8:46:90:70:ed:40:3a:3c:9b:8b:97:cf:c2:f3:31:e4:f4: + 4c:7d:7a:f8:7e:ee:3e:47:cf:de:d8:8d:ce:3c:d0:96:36:5f: + 8e:eb:23:de:de:95:cc:73:5b:e3:1e:0e:30:55:86:32:03:74: + 35:74:bd:96:91:78:c8:75:27:0b:3e:8e:2b:0b:69:a9:27:d2: + 1b:cf:8a:74:d9:ce:90:e2:b6:fa:bb:d9:9b:18:f5:42:49:c9: + 86:91:14:fe:e2:df:3a:63:f2:c8:31:57:7c:17:bb:d8:73:b5: + e5:4f:2a:b7:97:f2:c0:ef:12:db:15:16:d2:c2:8e:e9:0f:c7: + 63:3f:9d:52 -----BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUMbWvdUeWTEuSK5ppK5B6S+7LzO0wDQYJKoZIhvcNAQEL +MIIDjzCCAnegAwIBAgIUf+DoJ1Z3m9o53/Ou4WkW3phP/SQwDQYJKoZIhvcNAQEL BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y -MDAxMjkxNTQzNTZaFw0yMzAxMjgxNTQzNTZaMFcxCzAJBgNVBAYTAkFVMRMwEQYD +NDA0MjAxMTA5MjFaFw0yNzA0MjAxMTA5MjFaMFcxCzAJBgNVBAYTAkFVMRMwEQYD VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDOenkwnxjXDl7yD3Io23c+t777WOBNT+/XkElOCNCfp8ogbB6JVfZz2MW6 -MeNqfemnotsMM12OvnebL3QGHDVFFdsQ9Gnt3LaEttHhC/Xg8FSHk41g0D/guaJc -XVhhgJBTW7hHl/vwCT8H/07z4ItGvILjttKCy89PqzxbodEoYNwNc/VSsQy9Eox/ -y2Z01EuiemcdvTMhZd6u8O5d5d0C52gbLmLeIkVmwOzOO7+9tJhEGcNJFGna4U5e -Phu2Hk8PiI4B4VpvlXMIxpOJQWXrxnhwNRf6SSKWCEZOopH82YUPmILMPAiIubjI -s2y2XwQXtt1Kj16RJriHngOn68gNAgMBAAGjUzBRMB0GA1UdDgQWBBS2JI9T0SRm -8h3qTzcr8Do6eLpdRTAfBgNVHSMEGDAWgBS2JI9T0SRm8h3qTzcr8Do6eLpdRTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAojDNYMlZaZvjr2CQX -OBVSY6ZJjiKkI1c4Ihx1Dx7RqJeGYUnqwrzRU5HTlvAvLUHfFMIV9ahXeuRlpsJ5 -DY2kjTwtyTozF8IPPEwc8DQrFhuO2qaZOzvCbYWqO5uqx0TKxXN227xv85pHT2if -7IwhJwKInQg1d56heZ6Isz0tFysXeY7NJZ3iYgBg7PsBTeDiAPAW3XvXY2naSXui -vp12fnfcH7GF8QrcuCiqrgDfNASsfkONfCDBmOPJdLmxoGHcXxwORrGM2XQkPeAU -c/exZclzK5ke4rWkm0DoC/soHkCPkwE7LFcS0Uzvmyb5ORPSycBVS5mS13cz7YAy -L/wk +AoIBAQCgi7rssuL8R7mo3VmpR0k3CrBhxe7uc7z2rZrM8uPZgoanqe9bnS65QhjG +RgG5PRUMiXvCerwX3lRDSolVU6HPfFt60sdF/tra06bFJFXXaYuF29G/R0NFu7R0 +SFCRmYGEtdY11GbG0Gm0e474R/T3R6ilAYdA1EFXTpMTCkEiFC3j3KwAcC3Rzwgo +2Xtk091Irc51V5A/ncPTjNOARJZOcXF82/fRHzoJLRNM3scZXMG2y8qR+NZ+IjKq +1FFdaWrfNIGZjP0OIjAXiSAV/7UAqUmq89Vl5cidv8eP+n9Wd+BdtIam13DW9naL +/oiEKysogSdZRQg7OVLQ3wCduFTHAgMBAAGjUzBRMB0GA1UdDgQWBBSPtJTIf8vv +AImy9sG+REscElQ7KDAfBgNVHSMEGDAWgBSPtJTIf8vvAImy9sG+REscElQ7KDAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAonSJR8joLSAyM+ou+ +pDtd3DPZB/D4GsuZFVmhhwxcjt6olleMUMK67/3EAwvYbZ46ApyZQy61v5vWgRpA +sWFQCz/ahIXiVcemiNntuN+2MwM8R+P4rN/Mqyp6oL9zbCWXpH1gHSRsMdi2mPGK +LjzwftH6ZQpqZOO9P627jp0CuEaQcO1AOjybi5fPwvMx5PRMfXr4fu4+R8/e2I3O +PNCWNl+O6yPe3pXMc1vjHg4wVYYyA3Q1dL2WkXjIdScLPo4rC2mpJ9Ibz4p02c6Q +4rb6u9mbGPVCScmGkRT+4t86Y/LIMVd8F7vYc7XlTyq3l/LA7xLbFRbSwo7pD8dj +P51S -----END CERTIFICATE----- diff --git a/src/test/ruby/x509/demoCA/cacert.pem b/src/test/ruby/x509/demoCA/cacert.pem index 521f6529..e83dd63a 100644 --- a/src/test/ruby/x509/demoCA/cacert.pem +++ b/src/test/ruby/x509/demoCA/cacert.pem @@ -2,79 +2,79 @@ Certificate: Data: Version: 3 (0x2) Serial Number: - 31:b5:af:75:47:96:4c:4b:92:2b:9a:69:2b:90:7a:4b:ee:cb:cc:ed + 7f:e0:e8:27:56:77:9b:da:39:df:f3:ae:e1:69:16:de:98:4f:fd:24 Signature Algorithm: sha256WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca Validity - Not Before: Jan 29 15:43:56 2020 GMT - Not After : Jan 28 15:43:56 2023 GMT + Not Before: Apr 20 11:09:21 2024 GMT + Not After : Apr 20 11:09:21 2027 GMT Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) + Public-Key: (2048 bit) Modulus: - 00:ce:7a:79:30:9f:18:d7:0e:5e:f2:0f:72:28:db: - 77:3e:b7:be:fb:58:e0:4d:4f:ef:d7:90:49:4e:08: - d0:9f:a7:ca:20:6c:1e:89:55:f6:73:d8:c5:ba:31: - e3:6a:7d:e9:a7:a2:db:0c:33:5d:8e:be:77:9b:2f: - 74:06:1c:35:45:15:db:10:f4:69:ed:dc:b6:84:b6: - d1:e1:0b:f5:e0:f0:54:87:93:8d:60:d0:3f:e0:b9: - a2:5c:5d:58:61:80:90:53:5b:b8:47:97:fb:f0:09: - 3f:07:ff:4e:f3:e0:8b:46:bc:82:e3:b6:d2:82:cb: - cf:4f:ab:3c:5b:a1:d1:28:60:dc:0d:73:f5:52:b1: - 0c:bd:12:8c:7f:cb:66:74:d4:4b:a2:7a:67:1d:bd: - 33:21:65:de:ae:f0:ee:5d:e5:dd:02:e7:68:1b:2e: - 62:de:22:45:66:c0:ec:ce:3b:bf:bd:b4:98:44:19: - c3:49:14:69:da:e1:4e:5e:3e:1b:b6:1e:4f:0f:88: - 8e:01:e1:5a:6f:95:73:08:c6:93:89:41:65:eb:c6: - 78:70:35:17:fa:49:22:96:08:46:4e:a2:91:fc:d9: - 85:0f:98:82:cc:3c:08:88:b9:b8:c8:b3:6c:b6:5f: - 04:17:b6:dd:4a:8f:5e:91:26:b8:87:9e:03:a7:eb: - c8:0d + 00:a0:8b:ba:ec:b2:e2:fc:47:b9:a8:dd:59:a9:47: + 49:37:0a:b0:61:c5:ee:ee:73:bc:f6:ad:9a:cc:f2: + e3:d9:82:86:a7:a9:ef:5b:9d:2e:b9:42:18:c6:46: + 01:b9:3d:15:0c:89:7b:c2:7a:bc:17:de:54:43:4a: + 89:55:53:a1:cf:7c:5b:7a:d2:c7:45:fe:da:da:d3: + a6:c5:24:55:d7:69:8b:85:db:d1:bf:47:43:45:bb: + b4:74:48:50:91:99:81:84:b5:d6:35:d4:66:c6:d0: + 69:b4:7b:8e:f8:47:f4:f7:47:a8:a5:01:87:40:d4: + 41:57:4e:93:13:0a:41:22:14:2d:e3:dc:ac:00:70: + 2d:d1:cf:08:28:d9:7b:64:d3:dd:48:ad:ce:75:57: + 90:3f:9d:c3:d3:8c:d3:80:44:96:4e:71:71:7c:db: + f7:d1:1f:3a:09:2d:13:4c:de:c7:19:5c:c1:b6:cb: + ca:91:f8:d6:7e:22:32:aa:d4:51:5d:69:6a:df:34: + 81:99:8c:fd:0e:22:30:17:89:20:15:ff:b5:00:a9: + 49:aa:f3:d5:65:e5:c8:9d:bf:c7:8f:fa:7f:56:77: + e0:5d:b4:86:a6:d7:70:d6:f6:76:8b:fe:88:84:2b: + 2b:28:81:27:59:45:08:3b:39:52:d0:df:00:9d:b8: + 54:c7 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Subject Key Identifier: - B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 X509v3 Authority Key Identifier: - keyid:B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 - + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: sha256WithRSAEncryption - 28:8c:33:58:32:56:5a:66:f8:eb:d8:24:17:38:15:52:63:a6: - 49:8e:22:a4:23:57:38:22:1c:75:0f:1e:d1:a8:97:86:61:49: - ea:c2:bc:d1:53:91:d3:96:f0:2f:2d:41:df:14:c2:15:f5:a8: - 57:7a:e4:65:a6:c2:79:0d:8d:a4:8d:3c:2d:c9:3a:33:17:c2: - 0f:3c:4c:1c:f0:34:2b:16:1b:8e:da:a6:99:3b:3b:c2:6d:85: - aa:3b:9b:aa:c7:44:ca:c5:73:76:db:bc:6f:f3:9a:47:4f:68: - 9f:ec:8c:21:27:02:88:9d:08:35:77:9e:a1:79:9e:88:b3:3d: - 2d:17:2b:17:79:8e:cd:25:9d:e2:62:00:60:ec:fb:01:4d:e0: - e2:00:f0:16:dd:7b:d7:63:69:da:49:7b:a2:be:9d:76:7e:77: - dc:1f:b1:85:f1:0a:dc:b8:28:aa:ae:00:df:34:04:ac:7e:43: - 8d:7c:20:c1:98:e3:c9:74:b9:b1:a0:61:dc:5f:1c:0e:46:b1: - 8c:d9:74:24:3d:e0:14:73:f7:b1:65:c9:73:2b:99:1e:e2:b5: - a4:9b:40:e8:0b:fb:28:1e:40:8f:93:01:3b:2c:57:12:d1:4c: - ef:9b:26:f9:39:13:d2:c9:c0:55:4b:99:92:d7:77:33:ed:80: - 32:2f:fc:24 + Signature Value: + 28:9d:22:51:f2:3a:0b:48:0c:8c:fa:8b:be:a4:3b:5d:dc:33: + d9:07:f0:f8:1a:cb:99:15:59:a1:87:0c:5c:8e:de:a8:96:57: + 8c:50:c2:ba:ef:fd:c4:03:0b:d8:6d:9e:3a:02:9c:99:43:2e: + b5:bf:9b:d6:81:1a:40:b1:61:50:0b:3f:da:84:85:e2:55:c7: + a6:88:d9:ed:b8:df:b6:33:03:3c:47:e3:f8:ac:df:cc:ab:2a: + 7a:a0:bf:73:6c:25:97:a4:7d:60:1d:24:6c:31:d8:b6:98:f1: + 8a:2e:3c:f0:7e:d1:fa:65:0a:6a:64:e3:bd:3f:ad:bb:8e:9d: + 02:b8:46:90:70:ed:40:3a:3c:9b:8b:97:cf:c2:f3:31:e4:f4: + 4c:7d:7a:f8:7e:ee:3e:47:cf:de:d8:8d:ce:3c:d0:96:36:5f: + 8e:eb:23:de:de:95:cc:73:5b:e3:1e:0e:30:55:86:32:03:74: + 35:74:bd:96:91:78:c8:75:27:0b:3e:8e:2b:0b:69:a9:27:d2: + 1b:cf:8a:74:d9:ce:90:e2:b6:fa:bb:d9:9b:18:f5:42:49:c9: + 86:91:14:fe:e2:df:3a:63:f2:c8:31:57:7c:17:bb:d8:73:b5: + e5:4f:2a:b7:97:f2:c0:ef:12:db:15:16:d2:c2:8e:e9:0f:c7: + 63:3f:9d:52 -----BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUMbWvdUeWTEuSK5ppK5B6S+7LzO0wDQYJKoZIhvcNAQEL +MIIDjzCCAnegAwIBAgIUf+DoJ1Z3m9o53/Ou4WkW3phP/SQwDQYJKoZIhvcNAQEL BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y -MDAxMjkxNTQzNTZaFw0yMzAxMjgxNTQzNTZaMFcxCzAJBgNVBAYTAkFVMRMwEQYD +NDA0MjAxMTA5MjFaFw0yNzA0MjAxMTA5MjFaMFcxCzAJBgNVBAYTAkFVMRMwEQYD VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDOenkwnxjXDl7yD3Io23c+t777WOBNT+/XkElOCNCfp8ogbB6JVfZz2MW6 -MeNqfemnotsMM12OvnebL3QGHDVFFdsQ9Gnt3LaEttHhC/Xg8FSHk41g0D/guaJc -XVhhgJBTW7hHl/vwCT8H/07z4ItGvILjttKCy89PqzxbodEoYNwNc/VSsQy9Eox/ -y2Z01EuiemcdvTMhZd6u8O5d5d0C52gbLmLeIkVmwOzOO7+9tJhEGcNJFGna4U5e -Phu2Hk8PiI4B4VpvlXMIxpOJQWXrxnhwNRf6SSKWCEZOopH82YUPmILMPAiIubjI -s2y2XwQXtt1Kj16RJriHngOn68gNAgMBAAGjUzBRMB0GA1UdDgQWBBS2JI9T0SRm -8h3qTzcr8Do6eLpdRTAfBgNVHSMEGDAWgBS2JI9T0SRm8h3qTzcr8Do6eLpdRTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAojDNYMlZaZvjr2CQX -OBVSY6ZJjiKkI1c4Ihx1Dx7RqJeGYUnqwrzRU5HTlvAvLUHfFMIV9ahXeuRlpsJ5 -DY2kjTwtyTozF8IPPEwc8DQrFhuO2qaZOzvCbYWqO5uqx0TKxXN227xv85pHT2if -7IwhJwKInQg1d56heZ6Isz0tFysXeY7NJZ3iYgBg7PsBTeDiAPAW3XvXY2naSXui -vp12fnfcH7GF8QrcuCiqrgDfNASsfkONfCDBmOPJdLmxoGHcXxwORrGM2XQkPeAU -c/exZclzK5ke4rWkm0DoC/soHkCPkwE7LFcS0Uzvmyb5ORPSycBVS5mS13cz7YAy -L/wk +AoIBAQCgi7rssuL8R7mo3VmpR0k3CrBhxe7uc7z2rZrM8uPZgoanqe9bnS65QhjG +RgG5PRUMiXvCerwX3lRDSolVU6HPfFt60sdF/tra06bFJFXXaYuF29G/R0NFu7R0 +SFCRmYGEtdY11GbG0Gm0e474R/T3R6ilAYdA1EFXTpMTCkEiFC3j3KwAcC3Rzwgo +2Xtk091Irc51V5A/ncPTjNOARJZOcXF82/fRHzoJLRNM3scZXMG2y8qR+NZ+IjKq +1FFdaWrfNIGZjP0OIjAXiSAV/7UAqUmq89Vl5cidv8eP+n9Wd+BdtIam13DW9naL +/oiEKysogSdZRQg7OVLQ3wCduFTHAgMBAAGjUzBRMB0GA1UdDgQWBBSPtJTIf8vv +AImy9sG+REscElQ7KDAfBgNVHSMEGDAWgBSPtJTIf8vvAImy9sG+REscElQ7KDAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAonSJR8joLSAyM+ou+ +pDtd3DPZB/D4GsuZFVmhhwxcjt6olleMUMK67/3EAwvYbZ46ApyZQy61v5vWgRpA +sWFQCz/ahIXiVcemiNntuN+2MwM8R+P4rN/Mqyp6oL9zbCWXpH1gHSRsMdi2mPGK +LjzwftH6ZQpqZOO9P627jp0CuEaQcO1AOjybi5fPwvMx5PRMfXr4fu4+R8/e2I3O +PNCWNl+O6yPe3pXMc1vjHg4wVYYyA3Q1dL2WkXjIdScLPo4rC2mpJ9Ibz4p02c6Q +4rb6u9mbGPVCScmGkRT+4t86Y/LIMVd8F7vYc7XlTyq3l/LA7xLbFRbSwo7pD8dj +P51S -----END CERTIFICATE----- diff --git a/src/test/ruby/x509/demoCA/careq.pem b/src/test/ruby/x509/demoCA/careq.pem index 3849225e..c19a58ed 100644 --- a/src/test/ruby/x509/demoCA/careq.pem +++ b/src/test/ruby/x509/demoCA/careq.pem @@ -1,16 +1,16 @@ -----BEGIN CERTIFICATE REQUEST----- MIICnDCCAYQCAQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVt -by5jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM56eTCfGNcOXvIP -cijbdz63vvtY4E1P79eQSU4I0J+nyiBsHolV9nPYxbox42p96aei2wwzXY6+d5sv -dAYcNUUV2xD0ae3ctoS20eEL9eDwVIeTjWDQP+C5olxdWGGAkFNbuEeX+/AJPwf/ -TvPgi0a8guO20oLLz0+rPFuh0Shg3A1z9VKxDL0SjH/LZnTUS6J6Zx29MyFl3q7w -7l3l3QLnaBsuYt4iRWbA7M47v720mEQZw0kUadrhTl4+G7YeTw+IjgHhWm+VcwjG -k4lBZevGeHA1F/pJIpYIRk6ikfzZhQ+Ygsw8CIi5uMizbLZfBBe23UqPXpEmuIee -A6fryA0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQDIXpxWzMCsMSktg4/9VjgF -xQPLlUT1/obwIKadXBpTvWWFrsRUMxuv1/esZtG2RrzqpGSG45uayioy9JthBOcq -6frY+sjjr9Z0PgP1z5J0fCy5kxXsBjm5Vtsc3AdQ7uhTQFnavTRTp/WQLVIuSrVI -gQMPDJcMdXEtgNFU7YfIMSCdx+2JlPxkT4ReZ65X/6Slm2RQkcj4Fijo4DNXJyXv -60/GgQI8KYZF9CoxcoCvZJvGCJzkyrn00udvBkjC4HlViqVOxtOLkqeMW/8+YQlL -jo7SKfTQToeSuhJjtWU/nbWaQ79TjxbVr4V37Ja7PxdCcwClB7nXPzQrDSlPvdD0 +by5jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKCLuuyy4vxHuajd +WalHSTcKsGHF7u5zvPatmszy49mChqep71udLrlCGMZGAbk9FQyJe8J6vBfeVENK +iVVToc98W3rSx0X+2trTpsUkVddpi4Xb0b9HQ0W7tHRIUJGZgYS11jXUZsbQabR7 +jvhH9PdHqKUBh0DUQVdOkxMKQSIULePcrABwLdHPCCjZe2TT3UitznVXkD+dw9OM +04BElk5xcXzb99EfOgktE0zexxlcwbbLypH41n4iMqrUUV1pat80gZmM/Q4iMBeJ +IBX/tQCpSarz1WXlyJ2/x4/6f1Z34F20hqbXcNb2dov+iIQrKyiBJ1lFCDs5UtDf +AJ24VMcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQCTcwtA28eCbhgz9xraP8UL +VVdlLXzXUJVIg1ekjLyp7blSyNRx4c/Eymw03QTZOJqG3J9h9NbQ1fpsGcRpiruh +qHI7gM+givDP+IjPIuz3kOToZtmJ3Nj25fKmBpGcy0ftcoJ9ZpwYcVzlgA4eT07v +XHhYX64b9EAJttKGB3sFfI1ecTaeZ9sRpAuIYm6jtfcsH/oEfFPVAs/FfHGcCy3B +aqvBOUfFbxqPPe+aDRYKnTcaKvtZIbg61IqRUpW2o+tBCnr/RJMV21jBaEIGnT2B +TLdwMZ2xniFRjIr9UEGNR0lC0HAufGAZBqRy4NPUT9128mz5kYnftafXC5Y5J7UB -----END CERTIFICATE REQUEST----- diff --git a/src/test/ruby/x509/demoCA/crlnumber b/src/test/ruby/x509/demoCA/crlnumber deleted file mode 100644 index 8a0f05e1..00000000 --- a/src/test/ruby/x509/demoCA/crlnumber +++ /dev/null @@ -1 +0,0 @@ -01 diff --git a/src/test/ruby/x509/demoCA/index.txt b/src/test/ruby/x509/demoCA/index.txt index 1dbd9887..8cb8f4c2 100644 --- a/src/test/ruby/x509/demoCA/index.txt +++ b/src/test/ruby/x509/demoCA/index.txt @@ -1 +1,3 @@ -V 230128154356Z 31B5AF7547964C4B922B9A692B907A4BEECBCCED unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=demo.ca +V 270420110921Z 7FE0E82756779BDA39DFF3AEE16916DE984FFD24 unknown /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=demo.ca +V 250420111357Z 7FE0E82756779BDA39DFF3AEE16916DE984FFD25 unknown /C=AU/ST=None/O=JRuby Dev Team/OU=JOSSL/CN=jruby.org +V 260725151415Z 7FE0E82756779BDA39DFF3AEE16916DE984FFD26 unknown /C=CS/ST=Bohemia/O=JRuby/OU=JOSSL/CN=jruby.org diff --git a/src/test/ruby/x509/demoCA/index.txt.old b/src/test/ruby/x509/demoCA/index.txt.old deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/ruby/x509/demoCA/newcerts/31B5AF7547964C4B922B9A692B907A4BEECBCCED.pem b/src/test/ruby/x509/demoCA/newcerts/31B5AF7547964C4B922B9A692B907A4BEECBCCED.pem deleted file mode 100644 index 521f6529..00000000 --- a/src/test/ruby/x509/demoCA/newcerts/31B5AF7547964C4B922B9A692B907A4BEECBCCED.pem +++ /dev/null @@ -1,80 +0,0 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: - 31:b5:af:75:47:96:4c:4b:92:2b:9a:69:2b:90:7a:4b:ee:cb:cc:ed - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca - Validity - Not Before: Jan 29 15:43:56 2020 GMT - Not After : Jan 28 15:43:56 2023 GMT - Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:ce:7a:79:30:9f:18:d7:0e:5e:f2:0f:72:28:db: - 77:3e:b7:be:fb:58:e0:4d:4f:ef:d7:90:49:4e:08: - d0:9f:a7:ca:20:6c:1e:89:55:f6:73:d8:c5:ba:31: - e3:6a:7d:e9:a7:a2:db:0c:33:5d:8e:be:77:9b:2f: - 74:06:1c:35:45:15:db:10:f4:69:ed:dc:b6:84:b6: - d1:e1:0b:f5:e0:f0:54:87:93:8d:60:d0:3f:e0:b9: - a2:5c:5d:58:61:80:90:53:5b:b8:47:97:fb:f0:09: - 3f:07:ff:4e:f3:e0:8b:46:bc:82:e3:b6:d2:82:cb: - cf:4f:ab:3c:5b:a1:d1:28:60:dc:0d:73:f5:52:b1: - 0c:bd:12:8c:7f:cb:66:74:d4:4b:a2:7a:67:1d:bd: - 33:21:65:de:ae:f0:ee:5d:e5:dd:02:e7:68:1b:2e: - 62:de:22:45:66:c0:ec:ce:3b:bf:bd:b4:98:44:19: - c3:49:14:69:da:e1:4e:5e:3e:1b:b6:1e:4f:0f:88: - 8e:01:e1:5a:6f:95:73:08:c6:93:89:41:65:eb:c6: - 78:70:35:17:fa:49:22:96:08:46:4e:a2:91:fc:d9: - 85:0f:98:82:cc:3c:08:88:b9:b8:c8:b3:6c:b6:5f: - 04:17:b6:dd:4a:8f:5e:91:26:b8:87:9e:03:a7:eb: - c8:0d - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Subject Key Identifier: - B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 - X509v3 Authority Key Identifier: - keyid:B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 - - X509v3 Basic Constraints: critical - CA:TRUE - Signature Algorithm: sha256WithRSAEncryption - 28:8c:33:58:32:56:5a:66:f8:eb:d8:24:17:38:15:52:63:a6: - 49:8e:22:a4:23:57:38:22:1c:75:0f:1e:d1:a8:97:86:61:49: - ea:c2:bc:d1:53:91:d3:96:f0:2f:2d:41:df:14:c2:15:f5:a8: - 57:7a:e4:65:a6:c2:79:0d:8d:a4:8d:3c:2d:c9:3a:33:17:c2: - 0f:3c:4c:1c:f0:34:2b:16:1b:8e:da:a6:99:3b:3b:c2:6d:85: - aa:3b:9b:aa:c7:44:ca:c5:73:76:db:bc:6f:f3:9a:47:4f:68: - 9f:ec:8c:21:27:02:88:9d:08:35:77:9e:a1:79:9e:88:b3:3d: - 2d:17:2b:17:79:8e:cd:25:9d:e2:62:00:60:ec:fb:01:4d:e0: - e2:00:f0:16:dd:7b:d7:63:69:da:49:7b:a2:be:9d:76:7e:77: - dc:1f:b1:85:f1:0a:dc:b8:28:aa:ae:00:df:34:04:ac:7e:43: - 8d:7c:20:c1:98:e3:c9:74:b9:b1:a0:61:dc:5f:1c:0e:46:b1: - 8c:d9:74:24:3d:e0:14:73:f7:b1:65:c9:73:2b:99:1e:e2:b5: - a4:9b:40:e8:0b:fb:28:1e:40:8f:93:01:3b:2c:57:12:d1:4c: - ef:9b:26:f9:39:13:d2:c9:c0:55:4b:99:92:d7:77:33:ed:80: - 32:2f:fc:24 ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUMbWvdUeWTEuSK5ppK5B6S+7LzO0wDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y -MDAxMjkxNTQzNTZaFw0yMzAxMjgxNTQzNTZaMFcxCzAJBgNVBAYTAkFVMRMwEQYD -VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM -dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDOenkwnxjXDl7yD3Io23c+t777WOBNT+/XkElOCNCfp8ogbB6JVfZz2MW6 -MeNqfemnotsMM12OvnebL3QGHDVFFdsQ9Gnt3LaEttHhC/Xg8FSHk41g0D/guaJc -XVhhgJBTW7hHl/vwCT8H/07z4ItGvILjttKCy89PqzxbodEoYNwNc/VSsQy9Eox/ -y2Z01EuiemcdvTMhZd6u8O5d5d0C52gbLmLeIkVmwOzOO7+9tJhEGcNJFGna4U5e -Phu2Hk8PiI4B4VpvlXMIxpOJQWXrxnhwNRf6SSKWCEZOopH82YUPmILMPAiIubjI -s2y2XwQXtt1Kj16RJriHngOn68gNAgMBAAGjUzBRMB0GA1UdDgQWBBS2JI9T0SRm -8h3qTzcr8Do6eLpdRTAfBgNVHSMEGDAWgBS2JI9T0SRm8h3qTzcr8Do6eLpdRTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAojDNYMlZaZvjr2CQX -OBVSY6ZJjiKkI1c4Ihx1Dx7RqJeGYUnqwrzRU5HTlvAvLUHfFMIV9ahXeuRlpsJ5 -DY2kjTwtyTozF8IPPEwc8DQrFhuO2qaZOzvCbYWqO5uqx0TKxXN227xv85pHT2if -7IwhJwKInQg1d56heZ6Isz0tFysXeY7NJZ3iYgBg7PsBTeDiAPAW3XvXY2naSXui -vp12fnfcH7GF8QrcuCiqrgDfNASsfkONfCDBmOPJdLmxoGHcXxwORrGM2XQkPeAU -c/exZclzK5ke4rWkm0DoC/soHkCPkwE7LFcS0Uzvmyb5ORPSycBVS5mS13cz7YAy -L/wk ------END CERTIFICATE----- diff --git a/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD24.pem b/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD24.pem new file mode 100644 index 00000000..e83dd63a --- /dev/null +++ b/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD24.pem @@ -0,0 +1,80 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 7f:e0:e8:27:56:77:9b:da:39:df:f3:ae:e1:69:16:de:98:4f:fd:24 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Validity + Not Before: Apr 20 11:09:21 2024 GMT + Not After : Apr 20 11:09:21 2027 GMT + Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:a0:8b:ba:ec:b2:e2:fc:47:b9:a8:dd:59:a9:47: + 49:37:0a:b0:61:c5:ee:ee:73:bc:f6:ad:9a:cc:f2: + e3:d9:82:86:a7:a9:ef:5b:9d:2e:b9:42:18:c6:46: + 01:b9:3d:15:0c:89:7b:c2:7a:bc:17:de:54:43:4a: + 89:55:53:a1:cf:7c:5b:7a:d2:c7:45:fe:da:da:d3: + a6:c5:24:55:d7:69:8b:85:db:d1:bf:47:43:45:bb: + b4:74:48:50:91:99:81:84:b5:d6:35:d4:66:c6:d0: + 69:b4:7b:8e:f8:47:f4:f7:47:a8:a5:01:87:40:d4: + 41:57:4e:93:13:0a:41:22:14:2d:e3:dc:ac:00:70: + 2d:d1:cf:08:28:d9:7b:64:d3:dd:48:ad:ce:75:57: + 90:3f:9d:c3:d3:8c:d3:80:44:96:4e:71:71:7c:db: + f7:d1:1f:3a:09:2d:13:4c:de:c7:19:5c:c1:b6:cb: + ca:91:f8:d6:7e:22:32:aa:d4:51:5d:69:6a:df:34: + 81:99:8c:fd:0e:22:30:17:89:20:15:ff:b5:00:a9: + 49:aa:f3:d5:65:e5:c8:9d:bf:c7:8f:fa:7f:56:77: + e0:5d:b4:86:a6:d7:70:d6:f6:76:8b:fe:88:84:2b: + 2b:28:81:27:59:45:08:3b:39:52:d0:df:00:9d:b8: + 54:c7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 + X509v3 Authority Key Identifier: + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 28:9d:22:51:f2:3a:0b:48:0c:8c:fa:8b:be:a4:3b:5d:dc:33: + d9:07:f0:f8:1a:cb:99:15:59:a1:87:0c:5c:8e:de:a8:96:57: + 8c:50:c2:ba:ef:fd:c4:03:0b:d8:6d:9e:3a:02:9c:99:43:2e: + b5:bf:9b:d6:81:1a:40:b1:61:50:0b:3f:da:84:85:e2:55:c7: + a6:88:d9:ed:b8:df:b6:33:03:3c:47:e3:f8:ac:df:cc:ab:2a: + 7a:a0:bf:73:6c:25:97:a4:7d:60:1d:24:6c:31:d8:b6:98:f1: + 8a:2e:3c:f0:7e:d1:fa:65:0a:6a:64:e3:bd:3f:ad:bb:8e:9d: + 02:b8:46:90:70:ed:40:3a:3c:9b:8b:97:cf:c2:f3:31:e4:f4: + 4c:7d:7a:f8:7e:ee:3e:47:cf:de:d8:8d:ce:3c:d0:96:36:5f: + 8e:eb:23:de:de:95:cc:73:5b:e3:1e:0e:30:55:86:32:03:74: + 35:74:bd:96:91:78:c8:75:27:0b:3e:8e:2b:0b:69:a9:27:d2: + 1b:cf:8a:74:d9:ce:90:e2:b6:fa:bb:d9:9b:18:f5:42:49:c9: + 86:91:14:fe:e2:df:3a:63:f2:c8:31:57:7c:17:bb:d8:73:b5: + e5:4f:2a:b7:97:f2:c0:ef:12:db:15:16:d2:c2:8e:e9:0f:c7: + 63:3f:9d:52 +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIUf+DoJ1Z3m9o53/Ou4WkW3phP/SQwDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y +NDA0MjAxMTA5MjFaFw0yNzA0MjAxMTA5MjFaMFcxCzAJBgNVBAYTAkFVMRMwEQYD +VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM +dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCgi7rssuL8R7mo3VmpR0k3CrBhxe7uc7z2rZrM8uPZgoanqe9bnS65QhjG +RgG5PRUMiXvCerwX3lRDSolVU6HPfFt60sdF/tra06bFJFXXaYuF29G/R0NFu7R0 +SFCRmYGEtdY11GbG0Gm0e474R/T3R6ilAYdA1EFXTpMTCkEiFC3j3KwAcC3Rzwgo +2Xtk091Irc51V5A/ncPTjNOARJZOcXF82/fRHzoJLRNM3scZXMG2y8qR+NZ+IjKq +1FFdaWrfNIGZjP0OIjAXiSAV/7UAqUmq89Vl5cidv8eP+n9Wd+BdtIam13DW9naL +/oiEKysogSdZRQg7OVLQ3wCduFTHAgMBAAGjUzBRMB0GA1UdDgQWBBSPtJTIf8vv +AImy9sG+REscElQ7KDAfBgNVHSMEGDAWgBSPtJTIf8vvAImy9sG+REscElQ7KDAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAonSJR8joLSAyM+ou+ +pDtd3DPZB/D4GsuZFVmhhwxcjt6olleMUMK67/3EAwvYbZ46ApyZQy61v5vWgRpA +sWFQCz/ahIXiVcemiNntuN+2MwM8R+P4rN/Mqyp6oL9zbCWXpH1gHSRsMdi2mPGK +LjzwftH6ZQpqZOO9P627jp0CuEaQcO1AOjybi5fPwvMx5PRMfXr4fu4+R8/e2I3O +PNCWNl+O6yPe3pXMc1vjHg4wVYYyA3Q1dL2WkXjIdScLPo4rC2mpJ9Ibz4p02c6Q +4rb6u9mbGPVCScmGkRT+4t86Y/LIMVd8F7vYc7XlTyq3l/LA7xLbFRbSwo7pD8dj +P51S +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD25.pem b/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD25.pem new file mode 100644 index 00000000..b9309714 --- /dev/null +++ b/src/test/ruby/x509/demoCA/newcerts/7FE0E82756779BDA39DFF3AEE16916DE984FFD25.pem @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 7f:e0:e8:27:56:77:9b:da:39:df:f3:ae:e1:69:16:de:98:4f:fd:25 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Validity + Not Before: Apr 20 11:13:57 2024 GMT + Not After : Apr 20 11:13:57 2025 GMT + Subject: C=AU, ST=None, O=JRuby Dev Team, OU=JOSSL, CN=jruby.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b3:83:9b:f8:e7:5d:25:cd:ac:dc:19:aa:46:ce: + ff:66:30:b0:9d:da:1f:24:32:b1:9a:82:fc:bb:d0: + 4a:38:96:fe:b3:64:51:92:a0:11:5c:51:4f:f3:dc: + d9:29:5e:25:ed:8b:17:35:6b:b2:01:0a:10:ce:ea: + 4d:67:7f:b3:a1:12:6d:dd:6e:31:11:12:a7:56:98: + 4a:89:87:e7:e8:4d:23:aa:6a:28:6e:4f:b4:73:9b: + 0f:6d:e7:32:5b:50:46:0e:06:5d:96:e4:5b:23:b1: + b3:f3:a4:9b:12:ff:a1:7c:1a:ef:76:6a:b0:52:bf: + 03:1a:b8:7c:07:8d:f8:53:15:80:e5:37:38:05:01: + 01:ef:25:d7:86:ea:1d:79:0f:fc:00:35:0d:7d:4d: + 9c:9f:d8:d6:2e:42:df:72:13:8a:49:3a:59:ae:a7: + d9:a1:a7:92:74:ca:c1:ab:c5:47:04:ad:b1:3f:69: + 02:a4:09:e0:94:bb:80:54:81:e6:8a:bc:1c:db:db: + 60:87:66:77:8d:f9:2b:bf:74:ba:dc:3d:5a:20:fa: + 5a:fd:50:4b:a7:43:f3:e2:11:04:31:0a:42:69:1f: + 4c:6d:db:71:a0:5b:1a:e8:70:2c:69:3e:e9:ce:ce: + 4e:c1:83:3c:39:cb:2b:8d:45:72:0a:3d:b8:74:c8: + f7:a9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + A4:5D:C3:4D:EA:92:65:AE:F2:66:60:9B:E2:4C:EC:78:FD:CA:E9:A2 + X509v3 Authority Key Identifier: + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 3a:cf:7b:5c:f4:46:9b:dc:77:38:68:1d:a9:48:f0:01:68:9a: + 9f:f7:34:4a:63:8a:d1:50:d6:18:a7:b3:ad:13:9f:46:01:e6: + 89:7d:81:de:5f:49:1f:76:18:ec:23:07:fb:a2:3d:e0:eb:29: + 67:12:2a:c0:ea:a6:51:69:37:81:49:c0:6e:6e:73:db:7d:09: + 92:db:fe:5b:9f:3f:a0:3e:96:1c:2a:40:bc:9b:73:3b:38:59: + a4:e4:6b:07:a6:d3:3c:fd:48:07:fc:d8:3d:d4:e9:91:20:fd: + d5:a7:98:4f:ea:8b:ab:39:fa:f3:95:e0:cd:af:85:0b:85:ed: + 72:ad:e0:74:83:88:b1:1f:1e:a7:13:56:4a:b5:6a:c5:6d:81: + cd:e7:69:f2:b9:49:f0:50:ae:21:ff:12:af:2f:c4:2a:23:43: + c3:73:64:f6:1a:b7:0f:9a:7b:3a:1f:93:6d:e3:30:23:00:dc: + b1:f1:83:22:a2:e6:6a:b0:e5:89:6a:71:f6:76:c3:4d:1f:c7: + 4a:75:75:8a:85:6e:b6:11:89:9d:59:3a:ac:6a:bc:df:4e:ad: + 1e:db:4c:81:66:64:b8:8a:86:be:0e:f2:10:3e:94:63:e3:e5: + 97:9a:b1:24:6f:ea:ff:09:06:9c:e8:f7:38:e1:7e:32:5e:55: + ae:39:5b:fa +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIUf+DoJ1Z3m9o53/Ou4WkW3phP/SUwDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y +NDA0MjAxMTEzNTdaFw0yNTA0MjAxMTEzNTdaMFkxCzAJBgNVBAYTAkFVMQ0wCwYD +VQQIDAROb25lMRcwFQYDVQQKDA5KUnVieSBEZXYgVGVhbTEOMAwGA1UECwwFSk9T +U0wxEjAQBgNVBAMMCWpydWJ5Lm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALODm/jnXSXNrNwZqkbO/2YwsJ3aHyQysZqC/LvQSjiW/rNkUZKgEVxR +T/Pc2SleJe2LFzVrsgEKEM7qTWd/s6ESbd1uMRESp1aYSomH5+hNI6pqKG5PtHOb +D23nMltQRg4GXZbkWyOxs/OkmxL/oXwa73ZqsFK/Axq4fAeN+FMVgOU3OAUBAe8l +14bqHXkP/AA1DX1NnJ/Y1i5C33ITikk6Wa6n2aGnknTKwavFRwStsT9pAqQJ4JS7 +gFSB5oq8HNvbYIdmd435K790utw9WiD6Wv1QS6dD8+IRBDEKQmkfTG3bcaBbGuhw +LGk+6c7OTsGDPDnLK41Fcgo9uHTI96kCAwEAAaNNMEswCQYDVR0TBAIwADAdBgNV +HQ4EFgQUpF3DTeqSZa7yZmCb4kzseP3K6aIwHwYDVR0jBBgwFoAUj7SUyH/L7wCJ +svbBvkRLHBJUOygwDQYJKoZIhvcNAQELBQADggEBADrPe1z0RpvcdzhoHalI8AFo +mp/3NEpjitFQ1hins60Tn0YB5ol9gd5fSR92GOwjB/uiPeDrKWcSKsDqplFpN4FJ +wG5uc9t9CZLb/lufP6A+lhwqQLybczs4WaTkawem0zz9SAf82D3U6ZEg/dWnmE/q +i6s5+vOV4M2vhQuF7XKt4HSDiLEfHqcTVkq1asVtgc3nafK5SfBQriH/Eq8vxCoj +Q8NzZPYatw+aezofk23jMCMA3LHxgyKi5mqw5YlqcfZ2w00fx0p1dYqFbrYRiZ1Z +OqxqvN9OrR7bTIFmZLiKhr4O8hA+lGPj5ZeasSRv6v8JBpzo9zjhfjJeVa45W/o= +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/demoCA/private/cakey.pem b/src/test/ruby/x509/demoCA/private/cakey.pem index 116bc819..39b0a4f8 100644 --- a/src/test/ruby/x509/demoCA/private/cakey.pem +++ b/src/test/ruby/x509/demoCA/private/cakey.pem @@ -1,30 +1,30 @@ -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIpedk4pAqwYwCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECFb4LVhFjeA5BIIEyJpLui+jPZeT -5ROYlmBv05Doc4PzFEy873+HiBY360ndvB1b1bEnsSqkAKRitVD9CQPLjh4tYHfk -ava57lkEJmSZuXndhfnHlPtmhmTEsqCD+IphUi8kVen5U7eAdEZJngkVMZSsIMyj -4my8G9YcAGF6+C6owOHrL+kJRGYHO9fg0rOQIiB5m3lVbKKkUd4FcoHxumPkK06f -Yw69aQsXN/h9GJ32pssPC95DZTz/hSA6Pjd4HhJI5565uJ4XezMiSuyXdoNnZAvK -imPI3+BdD+jLjAW7oxUUSmycaS6GVprXyrqT/l0QrfjVkYRDg1FZrUaQIuM+G6Q2 -cjHbnZCEG7lH3ISDuW5ueBNn1nYR6vhscQhN0FKOlfsM+0z1CLwgYX9jTLtBEo0H -46MJ59xtMybkBKql4tQk3WaysxkqbhwewRuhVF7SQnyO2XbimsEEWmPWx80EE4ft -mp2kgZJFcX+E3QJqYKoLpqdmadtq5r/KteVgFHhhzG9YxYs2x470+SjKzSqO8rkR -cpQlqr6PnMYg2UiyiNC/E2WmKaU/RrUGWHcXe3OtIit+pRPZZs975SnO1Cs4t/p4 -UZ5NwgHCUCsbblLFRVWqxBcG99nDr34e+LdopEkxeLSyQ+EP8NLyPVx9fjE/7Es2 -XCc/NcraT0/upHJpBmqivoCBe8qJIiSLnOMo6oIbtAq7JyOQ0PZx0W5/xe/hzByo -XK047ZCiBOJesJkNCWsSH9PdMW8es8UOlDIWzPFs3vXSV8sZ/bGczta4ff67M+Zb -9sYPV41lUF5GAwc6DYQ9Py3nzNKJNlvbZpldbIsc3fre1l5lUIrUk5uTkaNqccq1 -bvXQoT9yy0j6MOKwCnr8x1QF2j/JFb4JsP052tVewQ6o401gZ1HkbZg5LJjEOx2Y -mG6jHvJWQ7MV1IIBYbJQVZwFc4GTtJP9yRlVIbEuvte+QCdJeBAkhFQU3S/PqL4I -gjFyID4reYcDnuSizw67DsNAuv1tojhJc3mAP33U6zjfDz2E7SxgRyl/eK5FJ8S4 -LuoVVjAleZ3iZKU4ObJSYxRdsU4+bzzdfVDrUfUWKDbKQa5mpd/KtSXyv/5Z4CF6 -tfdkOqLcydSuE32/POmBUY6dwGKUXdudNs+WmmEIFawSEbDe7+E21FUTPc93hGip -JlYAcAtQt2S1j8nPSmG+GvN2vWb4xy8FEeqLhXIDYPVcj/cN4yk+wbowr5ELy87u -TMwerDXmFVhkvgIg+2w35QCJHubCh1TsCkdBd+Z2fzV4GrqCgqBJAOXDI/CuJ1OT -jYNYCYZB0dNmJLHoqYJxYOD71R+DjV0eEPkOVLW8bf9NpiONzBHMOg9M7N5LVSkx -W6fZ4XuDFuiybOmRXkZk4jcL0B4yw2E3KhGdT3Q+jI7t6gn7KclyiBFXQ5vhOY9B -Aa1PiAJnNKGLP5n+jYDqsasc1pN/Ui5m3M1LCJR2DJquW3bXlbxjDUUNLMNK8dKX -RclfhAVi05nNfQWXFAMFfBGJMvhLTUpdu944tfvnQqjesr55bih4CjwY8MkKGM8I -Gr9MjixntWmLGmZhVhbFUIE6xX+SZg2Zb8wRwVYdvUHS2uK+X5bD5hM/rnJYJLQV -koWHTm8lGV3azn2hsUPdLg== +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIga/rK91RImECAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECKwVl8kx/xDEBIIEyJlHqU2PSAWV +vHlaaYalchpBH2aZ88In3JcFuQ8t+sTr8AmTL2PBg/KVEyiDLeCcWbAJcvpkM6y2 +GJjAxAYiibipVwbG0Zx/7YJqq3FdG64Bo+z8yI4AFk+lTTU9Kvezz20y+N7+qZuX +JBQu8yJsbMw7MMmsk1Ydt/nmu1gF/sX3r8eJ4ELi7cUPu9juBffXTtsE7nXJTeZL +j+7Ubh6YhF1JHmEz5b+6x16KZ971FHUkb7VS7BP9gK84LWfVSoLPOTRP66zIT8Ob +fi3EgRKjqE99YZFIkKWJx+zv0nmJz25dZMdXdTGZgVS7pfTavPHkdbAyqeEXa+RE +63RpPa4aW5rge54enfIl+EIxG6Xds9jn4oZJP4V8kHsmfx2JAziBnXp5Z5wBJXjk +NekM78sP3AnHVwtPbsmUP2r5IVA+l3AAYhckaCw8V65TrQZu86CWiUoOj5pDuJub +v65/+rrYYcrDdJTvrh86Ws0e1AjNZvV7TW2sgA+J29cuM1WigGFwV2iYsvvmU+EY +PoXSX10PRdg73k6NeJ75/653mbATp/35LPY1N7zGYmQap0Y8llErp+7m7au6LHsX +zxqp6PISAGXtJTQBngBBf0wgMYZlErzNXpcVglea9Xa8dw163sMKJ3F8dEB6gK9M +Gm7KRK1gM0wUbrA0i0fOasHmqTZKYJLWYcVaoqc9qQAdSuU9USWgriJl1rHe0g7q +PT/jbSKLFn6e5cYlcIy7lfXQRoReh6fT7HnN7noej3IGZ7+2u1iOQTVYo/RvHXOt +EcRS5AhycWoVa4p/hGsAdLm1/P5jJL96MgZUpwH1Wp/PkaZptn4fhAzsmLnthsSn +ArMl7+PLu3Sv/ojfPAWeuoZvCTe3imPoL/k3sMPseGeRUKuq8QzCNLrBTwCeagtF +kH3/JqZim8kmlvum+stCt2CusnfQX4PFF7aLjiZzr0mAouScQTJXNKDdYGzEIOhL +5iZ+RvzUAh42VVVj0BkDMbrWmr+FV4MFqF+Tw2EQB6jVQyVupRQfTIRvDy3DPKX7 +fLWNwOzVUCCIjJxPWCT8DmTpAmIVb6SsZ3uSh3LkKjMxKSAe6n0r/gkD65o/N1p1 +f5d+vCrUHp/BdHRV1L+8sYrHE0j8Df5xTVSgnbQkPFXqBU3WiwqfRY4eN7ZDGvS+ +/SSd1FLnLrieXEDfz9dVScbCrqAVD7IfRvvrE+IOV0/WnuV7gLN/ARYT0RF05JJd +KFqbT48exw4nV8RGGQHdQTdtQYiaSz5Pj+jqHX6xvjI7wxyeDpuQQK0FcBHzbHVg +fplulR0TAZzhh1cMk1KeKAugAelBhE6LYGViWa6oHLAYIygghE3sR3ey9CXMfjr8 +4M3eqZNnPODqt9fY40DEaxPRXsspSExfpWycRrl8pEMRtwi330E7mZ5Z2LYVtiNu +PYYi7LK+xa6MsftdSn/MsmumNMQqNIiu6ZDZ2iuRdHM1aBzdV8CJ66CSn3NvkYjt +zQj6kZweVFsRufcLinwUSO3s+w+6O0bmQTLbFsE7w9+cxHnrHuQxZXKrX2DIxgSU +CN1sGp+t/HZpguIegBvAdL6U3WTtZkYqOJgdPU7J6XDj2P5M6RW3RWiTsWhjbveU +AuJKvhA0WgNS4+XnyBwZNw== -----END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/ruby/x509/demoCA/serial b/src/test/ruby/x509/demoCA/serial index 1db15f72..e06d064f 100644 --- a/src/test/ruby/x509/demoCA/serial +++ b/src/test/ruby/x509/demoCA/serial @@ -1 +1 @@ -31B5AF7547964C4B922B9A692B907A4BEECBCCEE +7FE0E82756779BDA39DFF3AEE16916DE984FFD27 diff --git a/src/test/ruby/x509/dsa.crt b/src/test/ruby/x509/dsa.crt new file mode 100644 index 00000000..2c6fe496 --- /dev/null +++ b/src/test/ruby/x509/dsa.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFmjCCBUECCQCyVP+siddScDAJBgcqhkjOOAQDMB4xCzAJBgNVBAYTAkRFMQ8w +DQYDVQQHDAZCZXJsaW4wHhcNMjMwMzAzMTIyODQ2WhcNMjMwNDAyMTIyODQ2WjAe +MQswCQYDVQQGEwJERTEPMA0GA1UEBwwGQmVybGluMIIExzCCAzoGByqGSM44BAEw +ggMtAoIBgQDLKx9elgtAt8cgYcXWPIye512chP+wcJw4hDGjyHbtkdshsI4us/Sg +FruQ8/aCwXDGOHgn/nDJQ5np3/3NiIXcWkBCcGfmbRjc3T2P/Umu3O6JaK9ejvpJ +l7GQkZnQOrDE3VJz9/3hw7BqsbsEc5NW6ruefBwTI4EpEVxZrvKWa9K2eAeRqAGO +u2tosdqoVnl08u/quu2WQ7XIdeY9otmeKdmwGStgFd3TzjfB3d98VcuQbkBfem6g +HT26YyU+sLickWh7FqJkv8Nr9YgFRUEzxuKLa9gKXn1RIz71nPXaIvUfuFQNfnQf +0rdmZULdXcHRnZFxwkPn6N+46tEhiLtCKEQ+bfx7o/mfd4WlhMAeCtu8pw7kthr0 +bLo/ZKlbl6JIV9O3kGd4QeghB1UzH8WbiyV/EZYy84XpJxfDWTeBJLquLE0M/kKa ++kb/QkEWcPptN62WtD3MVZhBhuZLQBcPXrKfgM3522jAWlL6ryK9sC1L4KULBPZ/ +XeIti2dEjD0CIQDjLh4LaF4hfd7BLvj/TRs9Eq+W94ekiIJFuCP1RtR/vwKCAYEA +wqM29Tv5dl0IMKad/sX0Ui8ZqwUfudsSO5a3W8kv9ovxVzrfjjBIq2C8ysrI4bzQ +lfk9fKhlEj7ZEjBkBaDoVfvD9oY1BzQxiCl9nmNQwW6drV+x6A6CnvZh3mNgrnMn +TRUjdJEThjoaG+Z6yN2igEhPBjLrNOeh26CPzFu4r7tbFsjXUT1PDQkCD5yDSZLP +sUWoGJgh9rgw1UWgKn9n6fKALRSbeM1Z4NfmQK65eOrhSitMmgrcSUV/0Cnntcfd +PBYBYuuxzw7tO6exqKTMid7nRo0SjhRb/bCZmlsAnbTHdcYN2/tAyY3jt6aYuTXS +bvzZ5gAPxjeYGLyaTJzXvCaYfAzpbC0Ks2Q2SkZLrxW6unS/pRXFPBtNxDTN+OfS +PB4SErFyo8Xr2Do2CzigG5YLnTe+ELr31W5oY63PetE/OKoNVvSUDjFttt9MoDs7 +5kkzswVx/WsYfYOWll0/iINqteSSctQnQHzPMP2sLe4c3PomMve9bHyIBLDtWKhT +A4IBhQACggGAbjm99bX6Y6gvMrF6QbfNsdXeQLwPWrfeET/3CuWdbM5smPDQXzal +nM0tnZCDwOuLbT7PxDOHuw1oOMv5sYLupjblU8NoDGWzJAVeUKjfftuWptwrbgdh +qkE+vHfmIduHHqHIl+ZMhS/F3C8/u6CctCy5sO/iYuIKzyYE//tZpf+jnVQ4j79m +Sa4skFufzUDVu7CLWd4LYkHzjRXNBAnbjF0/WJKOdnCdYlSwkhPC3VqjBGOBlotb +B8UE0XjKlx7LiSWEWCckLLo7WAU88d5ZXJhhJ4jeY8pYvYyRJ+hqh9sMORRs7I5V +5K2ysI3QYIbXmhN4gHkl0J84TWQ4ZVkjKfyJYFDtyH8x13GCyaGYwRFVLlkSkxx3 +lKASIE3vdc8h5yUXlNjWoPtL9pPc9BqmtkKXEYtYkdDkkL/lYVJ0CV8R5wM+ccx4 +M2cyysvDFnRpe1uNlU/joC76In6I7pYQE9UOhVGxjyX2+ZqRXEgn924Rjbksfg7G +AU389kgxJPhDMAkGByqGSM44BAMDSAAwRQIgH1GIKxmcIj27rVD9B2GhTNClelzW +fCFwnp5AEY02RwsCIQDE0V6ITkW9xxaQz8/bjvlCmVTJtJFD9rtZVjWhmrIgqg== +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/ec-ca.crl b/src/test/ruby/x509/ec-ca.crl new file mode 100644 index 00000000..c349d383 --- /dev/null +++ b/src/test/ruby/x509/ec-ca.crl @@ -0,0 +1,7 @@ +-----BEGIN X509 CRL----- +MIHcMIGDAgEBMAoGCCqGSM49BAMCMBAxDjAMBgNVBAMTBWVjLWNhFw0yMzA1MDIx +NDIwNTFaGA8yMDczMDIyODE0MjA1MVowGzAZAggXW1l2cygQyxcNMjMwNTAyMTQy +MDUxWqAjMCEwHwYDVR0jBBgwFoAUttNRPFixOdwcEEs8Zc/AP+XGM8IwCgYIKoZI +zj0EAwIDSAAwRQIhAIY/kYfZbkAJUOQkXcJrGfeZLUYpt2mofamD2aHGhaE8AiAh +rW6t9BQ3xUCKHTODJHJHe+otaiwSCXoVI2jlJBcDWg== +-----END X509 CRL----- diff --git a/src/test/ruby/x509/ec-ca.crt b/src/test/ruby/x509/ec-ca.crt new file mode 100644 index 00000000..1c3ab3ba --- /dev/null +++ b/src/test/ruby/x509/ec-ca.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBWzCCAQCgAwIBAgIIF1tZdnMbfdcwCgYIKoZIzj0EAwIwEDEOMAwGA1UEAxMF +ZWMtY2EwIBcNMjMwNTAyMTQyMDUxWhgPMjA3MzA0MTkxNDIwNTFaMBAxDjAMBgNV +BAMTBWVjLWNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3xYZYfagw6booMq2 +L/4x2RKVgwWM4UbAbycJHuubBESVic8AApX1WcjOEKjQt+9GqVFAJxKzjlxGA+Hc +SVlpIaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLbTUTxYsTncHBBLPGXPwD/lxjPCMAoGCCqGSM49BAMCA0kAMEYCIQD5QgDE +1AijBncz7ItMv+q2vED1/AqNNY/whm71/wGK+QIhANkGiD6DdrydjEgVuFTvW/Kg +S122sk5XXx5zlCmZVZQA +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/ec.crt b/src/test/ruby/x509/ec.crt new file mode 100644 index 00000000..9531a7b5 --- /dev/null +++ b/src/test/ruby/x509/ec.crt @@ -0,0 +1,9 @@ +-----BEGIN CERTIFICATE----- +MIIBLDCB0gIJAJRzFaGbFWl5MAoGCCqGSM49BAMCMB4xCzAJBgNVBAYTAkRFMQ8w +DQYDVQQHDAZCZXJsaW4wHhcNMjMwMzAzMTIxMzU4WhcNMjMwNDAyMTIxMzU4WjAe +MQswCQYDVQQGEwJERTEPMA0GA1UEBwwGQmVybGluMFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAEtyX3CxW6sRambalSi0XwFLK4sIV7eJekaOX3hV05oGDl1umT84OL +HHctC4+VcJ5R98gsDA9x8c8jT3o09yqarjAKBggqhkjOPQQDAgNJADBGAiEA9ALb +jJ4w2bQ3QsxdneNYo1T3yUKjpPk4C2/wWpiU354CIQCAtuLX7Fwb2xfGXFzpOgK7 +OcwGN+Mb5xA9eR17/uq07Q== +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/javastore.ts b/src/test/ruby/x509/javastore.ts index 6cef946e..dfd844f7 100644 Binary files a/src/test/ruby/x509/javastore.ts and b/src/test/ruby/x509/javastore.ts differ diff --git a/src/test/ruby/x509/newcert.pem b/src/test/ruby/x509/newcert.pem index 521f6529..f49f6fc8 100644 --- a/src/test/ruby/x509/newcert.pem +++ b/src/test/ruby/x509/newcert.pem @@ -2,79 +2,101 @@ Certificate: Data: Version: 3 (0x2) Serial Number: - 31:b5:af:75:47:96:4c:4b:92:2b:9a:69:2b:90:7a:4b:ee:cb:cc:ed + 7f:e0:e8:27:56:77:9b:da:39:df:f3:ae:e1:69:16:de:98:4f:fd:26 Signature Algorithm: sha256WithRSAEncryption Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca Validity - Not Before: Jan 29 15:43:56 2020 GMT - Not After : Jan 28 15:43:56 2023 GMT - Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=demo.ca + Not Before: Jul 25 15:14:15 2025 GMT + Not After : Jul 25 15:14:15 2026 GMT + Subject: C=CS, ST=Bohemia, O=JRuby, OU=JOSSL, CN=jruby.org Subject Public Key Info: Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) + Public-Key: (4096 bit) Modulus: - 00:ce:7a:79:30:9f:18:d7:0e:5e:f2:0f:72:28:db: - 77:3e:b7:be:fb:58:e0:4d:4f:ef:d7:90:49:4e:08: - d0:9f:a7:ca:20:6c:1e:89:55:f6:73:d8:c5:ba:31: - e3:6a:7d:e9:a7:a2:db:0c:33:5d:8e:be:77:9b:2f: - 74:06:1c:35:45:15:db:10:f4:69:ed:dc:b6:84:b6: - d1:e1:0b:f5:e0:f0:54:87:93:8d:60:d0:3f:e0:b9: - a2:5c:5d:58:61:80:90:53:5b:b8:47:97:fb:f0:09: - 3f:07:ff:4e:f3:e0:8b:46:bc:82:e3:b6:d2:82:cb: - cf:4f:ab:3c:5b:a1:d1:28:60:dc:0d:73:f5:52:b1: - 0c:bd:12:8c:7f:cb:66:74:d4:4b:a2:7a:67:1d:bd: - 33:21:65:de:ae:f0:ee:5d:e5:dd:02:e7:68:1b:2e: - 62:de:22:45:66:c0:ec:ce:3b:bf:bd:b4:98:44:19: - c3:49:14:69:da:e1:4e:5e:3e:1b:b6:1e:4f:0f:88: - 8e:01:e1:5a:6f:95:73:08:c6:93:89:41:65:eb:c6: - 78:70:35:17:fa:49:22:96:08:46:4e:a2:91:fc:d9: - 85:0f:98:82:cc:3c:08:88:b9:b8:c8:b3:6c:b6:5f: - 04:17:b6:dd:4a:8f:5e:91:26:b8:87:9e:03:a7:eb: - c8:0d + 00:b7:f5:4e:f1:f1:11:58:b5:b7:10:e2:e6:0a:b6: + 25:f7:e3:bc:e0:c8:ce:aa:ac:8c:6d:98:8c:f3:e8: + 04:f6:83:75:77:c4:1f:61:f1:a4:de:38:cf:e6:b0: + da:4a:85:d7:0a:14:06:dd:2a:ff:48:1c:a3:76:e5: + ef:95:8c:20:8d:49:37:14:8c:c9:9b:fe:92:85:82: + aa:f9:9a:28:b1:21:33:6f:85:6e:80:9d:cf:17:79: + b1:6d:d5:95:34:e2:52:72:be:6b:2b:f7:3c:69:d5: + 11:2e:27:64:f5:98:5d:1b:40:4e:80:a2:3c:38:22: + de:a8:e9:2b:e3:b2:54:c7:4d:1e:51:5c:1b:49:1a: + 46:47:4f:53:02:0e:c1:f5:40:11:03:e3:3e:c7:dd: + 54:91:09:cb:d7:60:4f:89:36:60:23:f9:cc:be:ad: + 0d:cc:60:67:e8:4c:06:8a:4c:4c:fc:6f:2a:99:9d: + 23:56:19:cd:ce:78:4e:4f:a8:69:e7:dd:35:b4:7f: + a1:f2:3a:88:ed:a2:74:2e:83:e8:99:14:56:9f:45: + 60:43:0d:83:31:b6:f6:ff:1d:45:49:fa:91:6c:a2: + 4b:44:40:ae:77:85:55:0d:8a:cf:75:ca:75:dc:a9: + 2b:55:2b:c2:d6:15:7c:02:dd:22:fa:6b:6e:ed:fe: + 18:64:82:17:78:b2:82:5a:31:ef:ab:25:c6:1f:d5: + a2:bc:4b:bf:88:2c:5f:98:64:00:ae:96:a9:5f:a1: + 22:3a:37:9d:7d:44:5d:d4:f3:d6:b2:c5:cf:5a:50: + 5b:3f:b1:72:e5:87:2f:d1:44:37:35:c4:ef:bc:f2: + 8f:c4:87:f8:64:65:5f:69:24:94:9e:ea:9d:d2:c0: + 1b:2d:ea:17:0f:14:71:1e:8b:8d:a6:48:5b:1e:d7: + 95:48:3a:fd:c7:c1:3b:a0:e4:06:d6:38:15:13:0a: + 15:df:0d:69:86:c2:90:1e:9d:a8:d7:56:b3:88:2d: + ca:98:8f:88:d5:fa:6c:52:3a:bf:03:5a:59:53:46: + fe:6a:68:cb:9e:b3:f0:4f:b9:f5:01:2d:75:4a:00: + e8:3b:d6:25:4b:d3:4d:4a:fc:59:f2:b5:d0:60:92: + c0:23:a0:36:cb:eb:97:c3:68:80:b5:93:94:68:56: + 73:66:25:25:c9:44:3f:56:b3:5a:50:d6:80:b6:8f: + db:93:a2:c3:9e:b6:8c:70:48:1b:c5:92:44:2b:ca: + ab:59:c0:93:57:03:21:19:68:67:f9:6a:0d:4b:95: + fa:76:00:17:78:f4:b0:2f:77:18:92:81:59:16:f7: + d1:cf:40:14:bd:b9:6d:5e:50:dc:dc:67:21:fd:8b: + 32:3b:0d Exponent: 65537 (0x10001) X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE X509v3 Subject Key Identifier: - B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 + 79:85:4B:D2:26:9F:7B:14:09:A0:94:05:85:69:22:FC:AB:92:A8:6D X509v3 Authority Key Identifier: - keyid:B6:24:8F:53:D1:24:66:F2:1D:EA:4F:37:2B:F0:3A:3A:78:BA:5D:45 - - X509v3 Basic Constraints: critical - CA:TRUE + 8F:B4:94:C8:7F:CB:EF:00:89:B2:F6:C1:BE:44:4B:1C:12:54:3B:28 Signature Algorithm: sha256WithRSAEncryption - 28:8c:33:58:32:56:5a:66:f8:eb:d8:24:17:38:15:52:63:a6: - 49:8e:22:a4:23:57:38:22:1c:75:0f:1e:d1:a8:97:86:61:49: - ea:c2:bc:d1:53:91:d3:96:f0:2f:2d:41:df:14:c2:15:f5:a8: - 57:7a:e4:65:a6:c2:79:0d:8d:a4:8d:3c:2d:c9:3a:33:17:c2: - 0f:3c:4c:1c:f0:34:2b:16:1b:8e:da:a6:99:3b:3b:c2:6d:85: - aa:3b:9b:aa:c7:44:ca:c5:73:76:db:bc:6f:f3:9a:47:4f:68: - 9f:ec:8c:21:27:02:88:9d:08:35:77:9e:a1:79:9e:88:b3:3d: - 2d:17:2b:17:79:8e:cd:25:9d:e2:62:00:60:ec:fb:01:4d:e0: - e2:00:f0:16:dd:7b:d7:63:69:da:49:7b:a2:be:9d:76:7e:77: - dc:1f:b1:85:f1:0a:dc:b8:28:aa:ae:00:df:34:04:ac:7e:43: - 8d:7c:20:c1:98:e3:c9:74:b9:b1:a0:61:dc:5f:1c:0e:46:b1: - 8c:d9:74:24:3d:e0:14:73:f7:b1:65:c9:73:2b:99:1e:e2:b5: - a4:9b:40:e8:0b:fb:28:1e:40:8f:93:01:3b:2c:57:12:d1:4c: - ef:9b:26:f9:39:13:d2:c9:c0:55:4b:99:92:d7:77:33:ed:80: - 32:2f:fc:24 + Signature Value: + 73:c9:bd:4e:ea:79:53:c1:ea:6a:9f:69:f8:59:89:a7:5c:64: + eb:20:ae:93:78:a9:5d:01:8d:3d:dc:9a:b9:5f:7b:18:77:a5: + 55:1a:ad:cd:9a:24:4e:28:e7:33:55:e4:64:a4:e3:64:45:3f: + 6c:26:16:f0:56:c0:e8:de:0e:8c:27:aa:a1:0c:8a:74:9d:2c: + 3b:cc:5c:aa:91:67:e7:59:e4:83:79:eb:c5:71:06:c2:64:4f: + 96:27:26:7b:61:5f:59:99:88:91:bf:93:c4:4a:29:f3:63:a4: + e6:c0:54:d2:50:d3:9c:10:83:45:d9:f2:22:47:f6:79:0a:26: + 04:b8:26:bf:61:41:a6:9c:17:4f:dd:3c:51:7a:87:cc:c6:92: + 2c:7d:b8:95:41:e9:91:8c:5d:d9:ca:88:a6:fb:1a:43:c4:92: + 19:89:61:e0:b8:73:c4:94:38:b7:0c:d9:cb:28:16:c0:02:65: + 51:96:1c:d9:16:64:e4:30:1e:d2:6e:3c:1a:e6:b5:16:38:5d: + 3f:92:96:21:95:bf:ac:30:c3:85:3e:ee:8a:31:c7:8d:c5:19: + 08:1b:7c:74:c3:18:84:2f:e8:31:c2:c5:7b:da:8a:56:6f:51: + 6f:c8:e0:91:fb:d6:09:41:42:34:89:36:80:88:e3:80:29:21: + 26:ea:ad:d8 -----BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUMbWvdUeWTEuSK5ppK5B6S+7LzO0wDQYJKoZIhvcNAQEL +MIIEhTCCA22gAwIBAgIUf+DoJ1Z3m9o53/Ou4WkW3phP/SYwDQYJKoZIhvcNAQEL BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y -MDAxMjkxNTQzNTZaFw0yMzAxMjgxNTQzNTZaMFcxCzAJBgNVBAYTAkFVMRMwEQYD -VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM -dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDOenkwnxjXDl7yD3Io23c+t777WOBNT+/XkElOCNCfp8ogbB6JVfZz2MW6 -MeNqfemnotsMM12OvnebL3QGHDVFFdsQ9Gnt3LaEttHhC/Xg8FSHk41g0D/guaJc -XVhhgJBTW7hHl/vwCT8H/07z4ItGvILjttKCy89PqzxbodEoYNwNc/VSsQy9Eox/ -y2Z01EuiemcdvTMhZd6u8O5d5d0C52gbLmLeIkVmwOzOO7+9tJhEGcNJFGna4U5e -Phu2Hk8PiI4B4VpvlXMIxpOJQWXrxnhwNRf6SSKWCEZOopH82YUPmILMPAiIubjI -s2y2XwQXtt1Kj16RJriHngOn68gNAgMBAAGjUzBRMB0GA1UdDgQWBBS2JI9T0SRm -8h3qTzcr8Do6eLpdRTAfBgNVHSMEGDAWgBS2JI9T0SRm8h3qTzcr8Do6eLpdRTAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAojDNYMlZaZvjr2CQX -OBVSY6ZJjiKkI1c4Ihx1Dx7RqJeGYUnqwrzRU5HTlvAvLUHfFMIV9ahXeuRlpsJ5 -DY2kjTwtyTozF8IPPEwc8DQrFhuO2qaZOzvCbYWqO5uqx0TKxXN227xv85pHT2if -7IwhJwKInQg1d56heZ6Isz0tFysXeY7NJZ3iYgBg7PsBTeDiAPAW3XvXY2naSXui -vp12fnfcH7GF8QrcuCiqrgDfNASsfkONfCDBmOPJdLmxoGHcXxwORrGM2XQkPeAU -c/exZclzK5ke4rWkm0DoC/soHkCPkwE7LFcS0Uzvmyb5ORPSycBVS5mS13cz7YAy -L/wk +NTA3MjUxNTE0MTVaFw0yNjA3MjUxNTE0MTVaMFMxCzAJBgNVBAYTAkNTMRAwDgYD +VQQIDAdCb2hlbWlhMQ4wDAYDVQQKDAVKUnVieTEOMAwGA1UECwwFSk9TU0wxEjAQ +BgNVBAMMCWpydWJ5Lm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALf1TvHxEVi1txDi5gq2JffjvODIzqqsjG2YjPPoBPaDdXfEH2HxpN44z+aw2kqF +1woUBt0q/0gco3bl75WMII1JNxSMyZv+koWCqvmaKLEhM2+FboCdzxd5sW3VlTTi +UnK+ayv3PGnVES4nZPWYXRtAToCiPDgi3qjpK+OyVMdNHlFcG0kaRkdPUwIOwfVA +EQPjPsfdVJEJy9dgT4k2YCP5zL6tDcxgZ+hMBopMTPxvKpmdI1YZzc54Tk+oaefd +NbR/ofI6iO2idC6D6JkUVp9FYEMNgzG29v8dRUn6kWyiS0RArneFVQ2Kz3XKddyp +K1UrwtYVfALdIvprbu3+GGSCF3iyglox76slxh/VorxLv4gsX5hkAK6WqV+hIjo3 +nX1EXdTz1rLFz1pQWz+xcuWHL9FENzXE77zyj8SH+GRlX2kklJ7qndLAGy3qFw8U +cR6LjaZIWx7XlUg6/cfBO6DkBtY4FRMKFd8NaYbCkB6dqNdWs4gtypiPiNX6bFI6 +vwNaWVNG/mpoy56z8E+59QEtdUoA6DvWJUvTTUr8WfK10GCSwCOgNsvrl8NogLWT +lGhWc2YlJclEP1azWlDWgLaP25Oiw562jHBIG8WSRCvKq1nAk1cDIRloZ/lqDUuV ++nYAF3j0sC93GJKBWRb30c9AFL25bV5Q3NxnIf2LMjsNAgMBAAGjTTBLMAkGA1Ud +EwQCMAAwHQYDVR0OBBYEFHmFS9Imn3sUCaCUBYVpIvyrkqhtMB8GA1UdIwQYMBaA +FI+0lMh/y+8AibL2wb5ESxwSVDsoMA0GCSqGSIb3DQEBCwUAA4IBAQBzyb1O6nlT +wepqn2n4WYmnXGTrIK6TeKldAY093Jq5X3sYd6VVGq3NmiROKOczVeRkpONkRT9s +JhbwVsDo3g6MJ6qhDIp0nSw7zFyqkWfnWeSDeevFcQbCZE+WJyZ7YV9ZmYiRv5PE +SinzY6TmwFTSUNOcEINF2fIiR/Z5CiYEuCa/YUGmnBdP3TxReofMxpIsfbiVQemR +jF3Zyoim+xpDxJIZiWHguHPElDi3DNnLKBbAAmVRlhzZFmTkMB7Sbjwa5rUWOF0/ +kpYhlb+sMMOFPu6KMceNxRkIG3x0wxiEL+gxwsV72opWb1FvyOCR+9YJQUI0iTaA +iOOAKSEm6q3Y -----END CERTIFICATE----- diff --git a/src/test/ruby/x509/newkey.pem b/src/test/ruby/x509/newkey.pem deleted file mode 100644 index 24d7995f..00000000 --- a/src/test/ruby/x509/newkey.pem +++ /dev/null @@ -1,30 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQITMeN8JOVhNcCAggA -MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECMu+o/zvDf/VBIIEyDWUpe8c8o01 -xLiAiRcWtpzqI75qF6LBKte2VH9i+JtOUJZ19ky0swy4/raeNCvxHC16CS/qkZb3 -ZN/WkV1ELdsC/mgwIpg44JGoGItbE9Rgejaxez8rCnx2iUKGJ7IJuBUf47TPIW28 -27MueT4Hp29BY3a7MgneoNCuQKgt2n5Kj76wKH9/XvSgdWgq4uzZeFqNRnluM83B -OFPbcm0qeM4ANyQyVsUFxJ/9Q4CMvwY+hQFaVxVDaGqMUxmDR4xidaeZd0Y8uJNL -QB8rYZUbWewsO8bI9oS+EU23P2OK8U0UZR9D+78ljnm9q1E4/fvTRnxXJo41XEGW -kQyfEmFZaQ1d8QzVGk/5ND+q0M6cYdVxjl0MWMKy1/g7SezCRQzZCFMXTFII7LaT -U9Vp7etlbeFFAZBpeaDt25wOTB7rItiBDH0dWL03dD7hkdAxCRFc63V3d6b4jTsJ -FbAWUwRYr7He+128gGE4+u5NmygLhetGjIhUXhBH+RJMp+2ptxai9OC10uqA8x0O -T6PFs7K3vB1qwLscLnlE00e2IypdmVm6duFJY/kHBdVslUlmY8z67HhLYjnJexcK -ZoulRCVf6kNswqu4nQPSkTtoqPvVeydMsCoe+25Kv9fKJT4znA8twnpZF4o0tOqP -6Jol7wHICcPQxKbHIWWFG0Ml39kFJEsKOVykmK8mIRoglVRzv9I40FMEykq5eCUh -US3QVyMUpq0i7bxHEUNKl+txpjeWnIWGcc8RbKEnIrrkH1akqzEEKzeeCgrya5gm -3sK46REAXFwZbl9U0J1YzdT0Th0LJQI4AIsYT9IXdav/lppjyaJNJF5bYqp+flAQ -/urf7uVR/XjlGYQ1qJcFzo0BQxIp/+8Rf5lkZLlwwdTM1mp+3UxEWUJ8eF8aHTWl -Oe4PrpO0t3DFw1nCWWxCsoimfZNhq9boi001AdYrPicdLOSvh4BOsDG1ztD2CUJE -OYejLFD0t7wV81WnlORs3qVjqpuvRrzYVrOVXI0Y35NkKw3dX393hKTXAPlL+vxE -/F6tbvLL0qpfaxHOTqK5MZVNRqUqJsNPcICkxpODt/ZWcmbMdMQusjCieP1ZCtvo -JFCSnX8dszN/R4Of5eJ5HL5z5FjyzdZbw8I1+8hnLuflNT4OtiZJhh8zPe8k5TPL -SzlWZQNIO5niM+MICyMblJzzgFP8o3g1uiecAU2vKeJiygb7D6H/bPNh4pcVGvPB -EphtTnPRyryRGaRmyVM4ynwodBQe9ZyJ38MC7sOL+vtsUmZ9rlSqdec+YazrsAFv -yzCh1qojgT2rnmLYYmtLN67Hv/PSEe1+05fzDx/VTcNiCottCff8PSi8Cli2JcIw -MHIGcgHYBNT7nJqfwTxPHAXUv1UtJxdoDO6xgNCvVXtd6MKX73pXe6FR52EC+ymY -I8ylNpFuzg/egtju1gZFJD8eAUrzCbrmL8Gpgpsh2MLBQQTmfdtutHA50H0v7abu -iuiTMUvqocDSOF9Ow+1lxzG0SB7ON9AdcvsH5xshllOyEgSd5342b1FmtMsji1Tn -lNqkpURNaq4O2lqqYg4Prji3uDzsNPks5CBL5eKq+QN6neuWk+PS0Ys6IfQb7lnu -8RQS7L+HUxq5OKmyDo4iqA== ------END ENCRYPTED PRIVATE KEY----- diff --git a/src/test/ruby/x509/newreq.key b/src/test/ruby/x509/newreq.key new file mode 100644 index 00000000..5d8bd70a --- /dev/null +++ b/src/test/ruby/x509/newreq.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC39U7x8RFYtbcQ +4uYKtiX347zgyM6qrIxtmIzz6AT2g3V3xB9h8aTeOM/msNpKhdcKFAbdKv9IHKN2 +5e+VjCCNSTcUjMmb/pKFgqr5miixITNvhW6Anc8XebFt1ZU04lJyvmsr9zxp1REu +J2T1mF0bQE6Aojw4It6o6SvjslTHTR5RXBtJGkZHT1MCDsH1QBED4z7H3VSRCcvX +YE+JNmAj+cy+rQ3MYGfoTAaKTEz8byqZnSNWGc3OeE5PqGnn3TW0f6HyOojtonQu +g+iZFFafRWBDDYMxtvb/HUVJ+pFsoktEQK53hVUNis91ynXcqStVK8LWFXwC3SL6 +a27t/hhkghd4soJaMe+rJcYf1aK8S7+ILF+YZACulqlfoSI6N519RF3U89ayxc9a +UFs/sXLlhy/RRDc1xO+88o/Eh/hkZV9pJJSe6p3SwBst6hcPFHEei42mSFse15VI +Ov3HwTug5AbWOBUTChXfDWmGwpAenajXVrOILcqYj4jV+mxSOr8DWllTRv5qaMue +s/BPufUBLXVKAOg71iVL001K/FnytdBgksAjoDbL65fDaIC1k5RoVnNmJSXJRD9W +s1pQ1oC2j9uTosOetoxwSBvFkkQryqtZwJNXAyEZaGf5ag1Llfp2ABd49LAvdxiS +gVkW99HPQBS9uW1eUNzcZyH9izI7DQIDAQABAoICAAt/xBLk9NFMxUgYRt+oidsN +sK2EdUFp+SU+Afbv1qTqNsTpIjpnITKFmUlcXhOGe5i1A8Yo0pMEEfEevR6QsdWN +xr78G1DVqvxkMffZpm2GxnD+2uzN8Iy7yKw1oQFl8a1xMGsr0JT1AqvrPn5eVdjx +k3arAtn8zq+pqQVaDRP1mCLnKmAkH6cu0uDat05zVhzzbuXjdKPUWZpO+j8jglhS +scpsMDlJqsp0kNFtpOpVW0kFmkM0PbndqgIvZDS1jo6H4+xWXbTXu4Ik22dQ7tE/ +REiCEboL1oi/9agth5BOMieZZiPb1niQPW5UMjs8P+naYHAtdku03bjgT6uE1Kq9 +2ylflogdumRr2xldYaS7G2dfL496F5VXjYYXJv67H1iUm/Je2MI9DXgHatDZs2Id +jfl61WZLqUiG2g9RBs14NGUM5upEUT3fyrvVlhHgOoIWuyq63mKiGnPXwruekwsy +Tc0MIMSQ3vg9rFtAg4sa8Zlij65SaU8h+zVLlpxFPIRuFnp+GqPe+vHru+MNYxnr +UIEWqWul0F9JWzmUXe6+CBA6HQfPuAKiXh7lFpGG9NPdXGi1+fyInjjGs80q4uc+ +DJmYTRCXjUu+wCJqEX0vMOrEOINK2/Baqeawy2v2cUr3faIeuQE6TbFZqWXjH4xh +yXYPrrDuD2bE5GUOhXWBAoIBAQDpaqvcCp/MeIRt76tO+bD5Ay8W/9x/43jIITLZ +0rS2YcGJKIMJjEp6s4q0nzttPKBlOKxc6BeON8kFY9dg6bx8sI7Nf9Cpe7sS6rNT +DeT7mc/rlKj/mhif5O3JdPtL+jgzaYBpTaIOwKZia9C+fLYKeCe+tiWQ4TS+wLJH +xZPiBO8+xeEbDZuuZ2n84zCWVq2sDOr33HSe5WVULu6Rwd/Co5/FGjBdirKZ+DVy +436eMrQghJo/iGHuSVxdBDnqUsGKZiPog7DbgsqYxX3Q4HESipm6hfXStNIrUP8a ++yZXIfO2XLYdxe744XV5MIYvyZG9zwm4jcj0Saev+4Ck60xdAoIBAQDJwaL24pAD +Ex3UPPIPEzpvblSsH96djy9x84NY4sE83VnAAgRg02aOMwgAiky47asZYLqezrwp +1I+52gM0wYMvbhOf8lJSk+U0Fnb8u8tCr2s1nqmrqw8aKuDd2xLyvDySOU8FiB3Z +6LYQz+kesD132+MzRdwhCcnCjfGyqECLFgryRqFrUWBvukK2RZA1MHiD4X41FyHK +ZB4L0FCxnOuquDbbO+425MakWrH2Gx0zZBxOlA6HVizOujEv7nKoBP9VyfHmaN3c +gLajOLZZiZpul4ZrlK4B79kCtVNgOkzKCUm04gduXJyFcsAWXACFtAjvGQL19AAM +G+xqzGF4wz5xAoIBAG4B1yNqYmu0dP13Ei6zYSPKy1u0lJA1fcwcUWH1ezPlmJfy +3ucWFlgD3CBKV3ChPRrXfex/efKN+hCvQGetYScG8xaI4aeu57j/oipHhx7JHAP0 +WT21u1tIQoVKu9DQCcK5O0rlyrXXN9N06rmL/yOqA1lPcR759KjeGmfXB15jYvob +un7MiA3HMV19GX4RNeK7Z9YPMWtdw8bMI6XQUIvkH71+HUNIxeno4A/Yhek7Dkex +Fx1QQo9SRdSU1FLKU+rNPqkrv6OE3cToduaFkOTjK5aL4hI/JDpD/ycRLL2uA7Vz +tmULzxe+8mV+aA+HmUdylytCr23qdLxs9PFE8AUCggEAcD4V15v72noMIkM1t0VP +QSb0XD3ur2E1WAPNkumz75SFldiJbwStDXc+gG5weIFgquGxcSA6dlsXCqJLHlJ6 +FSz8JgO94Mr+vJey4q/nSNYmotH+Os8Tu88XAtYaGIi4efEAEWfEk7Jvd7LzUmti +BwiBwcifypGTeXVWfbo1DivZMygwLXA8YmwRKS5SKAUrRtNwT0DrP5mrTY87FVTj +lzqc6iLRSCDGkjESyIQVHr4XMXAVFR3QF8JmEvBpQShmlOegeDrnEwFYmj221OFE +1TnfQoKYu+Mfq+4M0IAesFeydbs5vyAuw98vFkFg2QFr5vjXONXiINp4EIJbx793 +QQKCAQEAjbc9cnMGBin3T8ePdgYi7ouuJppLsx4XU9YTZccEg5QKb7FNSqfZfnC6 +s9h2yLOaphB/tqgxdjlL7iYSRqBNxMFR+hSQvPBGiQogZacRdV0McPm0osmjsrhG +0N/UXWGx1joHO5WoiwfI984g1//whfywMxOKwq0nF5g+oLeMYDdt5DmuMAR7J5gm +t3/fnxjftsOGENo2L0iwShHzfaH0Bq46jamn9KuQmBnCG1LAUn+ugGYK+69PSLFm +8AJChve3rWhWdDKdtwtg5J5DpK9g6ySnJ2g5njhheaArUi/HejHQqbd5T3oM3nCy +eOI3Ol45btJyhVklr2SRTkiDakayaw== +-----END PRIVATE KEY----- diff --git a/src/test/ruby/x509/newreq.pem b/src/test/ruby/x509/newreq.pem index e53d74d5..fdaf840a 100644 --- a/src/test/ruby/x509/newreq.pem +++ b/src/test/ruby/x509/newreq.pem @@ -1,16 +1,27 @@ -----BEGIN CERTIFICATE REQUEST----- -MIICnDCCAYQCAQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx -ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVt -by5jYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANm46IGlvXov0867 -CB7JfqFpklsUZD4lbQrWTtlHSHV0YvNVASxe7w7wsWtxYwrV5Wto+nSlc61yymTr -nrilR50bC+KfhZGc5qJNIEHK95SVcLHNm5Qf0YdbpHUc/LkADCL1fyJmF8Bgv8zx -27BK2cQiXpFQ7GowHw/SkgLChiqVBmovohpUIMdjzprA7D3AS7OTb+J5ZCs92mMd -ifrlcuIzlYWVHNno7C/MvCxRiao5XM4In7qRTnekyZ59BSnoIl0Dw2O8wpml0twy -n8GnhgoYKLf69axU6NQEx+u1AG4x3ejIKCHmO/kxUF+Q4naTK3LZvOM660cdYJQV -E6K1LwMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQB6zWYKT0cjMEywoWtXWqVn -eRBLHSotr4fz3fGVKdjrqr3iqRZ4BW5jcdbTTeWHnYK5PRwvspPTkyUPw5Kki/21 -HFmhyWuoG128DC1vCnPHQvPztlUUH4EQxTT2iNRxy0ifeFKOHeXTmwq42S1MIokD -JwudVFEqLYSAVK5IVGqS7fOujXciHS26lBzeIN2fLf4FH36uE4WsYC0DT6UZldth -yNLbsEbNFiHE7Z75XLoN5j1ZDx9+pVp5ezuQxL6CpfvAgeLBEnjxhY/Y9n0UAUG+ -KZQh0sup5R4zsrWM0XpiQTbQ00YQbmnc57imepusNHhSDWMkZMVSCk1tHskvM+lW +MIIEmDCCAoACAQAwUzELMAkGA1UEBhMCQ1MxEDAOBgNVBAgMB0JvaGVtaWExDjAM +BgNVBAoMBUpSdWJ5MQ4wDAYDVQQLDAVKT1NTTDESMBAGA1UEAwwJanJ1Ynkub3Jn +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt/VO8fERWLW3EOLmCrYl +9+O84MjOqqyMbZiM8+gE9oN1d8QfYfGk3jjP5rDaSoXXChQG3Sr/SByjduXvlYwg +jUk3FIzJm/6ShYKq+ZoosSEzb4VugJ3PF3mxbdWVNOJScr5rK/c8adURLidk9Zhd +G0BOgKI8OCLeqOkr47JUx00eUVwbSRpGR09TAg7B9UARA+M+x91UkQnL12BPiTZg +I/nMvq0NzGBn6EwGikxM/G8qmZ0jVhnNznhOT6hp5901tH+h8jqI7aJ0LoPomRRW +n0VgQw2DMbb2/x1FSfqRbKJLRECud4VVDYrPdcp13KkrVSvC1hV8At0i+mtu7f4Y +ZIIXeLKCWjHvqyXGH9WivEu/iCxfmGQArpapX6EiOjedfURd1PPWssXPWlBbP7Fy +5Ycv0UQ3NcTvvPKPxIf4ZGVfaSSUnuqd0sAbLeoXDxRxHouNpkhbHteVSDr9x8E7 +oOQG1jgVEwoV3w1phsKQHp2o11aziC3KmI+I1fpsUjq/A1pZU0b+amjLnrPwT7n1 +AS11SgDoO9YlS9NNSvxZ8rXQYJLAI6A2y+uXw2iAtZOUaFZzZiUlyUQ/VrNaUNaA +to/bk6LDnraMcEgbxZJEK8qrWcCTVwMhGWhn+WoNS5X6dgAXePSwL3cYkoFZFvfR +z0AUvbltXlDc3Gch/YsyOw0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQAXleZN +2nEv3LxF6LQMkJxUH1jPM0xbQR49xU92nVr6sKnmONf1ZuguRGXwsnQMpdfaKCNh +k222mxeiKokjNRGr/sMiRrulLZnAOzVVOeJ4GiGhlkpR0dSCkMzhOS79q+uUc8TP +IwI5ZCoTlkapA7YfoJQbZwdUjWx2VrrRH+IWNzFf0n8mqCxs+BAOOdCSFTlXHOxE +NcFCzXT+mjPCMdiKLs4LKeRN0kasDuKQQHVgi6JTRdsucDwx3KzanvHV2aSmOFbP +uV5XQ5lWlNZtA2npsd7lcoPK3NJ56Kw5OXExR7HTU8ktZ1+WgjT5Q0ICSpiG4n5T +tAzhkj/IIxal5ZJy5x86MNtmdwekt8vlJLF8DCnB+MieP4prOaIkLviEzwk8VnDY +btHNT51y8gKqlsaygrjTtpOKMbYcixFDWs/c5qmsZfD0NSz8ZQg9HoBhiKlagDeT +fg3YU5Kw0MT6nbVgW5HZ9mfw4a3eBqZA5gJVx7tWSvutFzFk0lor2wxw4ZkCA88O +71QUJ3EgM2O71WdbdFaL6kGmAZz8qgKDHXbqi77eW8akHFv83DYjEMkogvKddryQ +B/TUvKt+mMewPWSuvHFcnjIf3NvzBuEleTswPu2b6FWJZZ13cIeIQmPCXX+GOZjj +ZIH34u+GXytujPEAQ1kyNtuQaVQ23FAFxXO+sw== -----END CERTIFICATE REQUEST----- diff --git a/src/test/ruby/x509/rsa.crt b/src/test/ruby/x509/rsa.crt new file mode 100644 index 00000000..a2d739b1 --- /dev/null +++ b/src/test/ruby/x509/rsa.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjzCCAnegAwIBAgIUMbWvdUeWTEuSK5ppK5B6S+7LzO0wDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHZGVtby5jYTAeFw0y +MDAxMjkxNTQzNTZaFw0yMzAxMjgxNTQzNTZaMFcxCzAJBgNVBAYTAkFVMRMwEQYD +VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM +dGQxEDAOBgNVBAMMB2RlbW8uY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDOenkwnxjXDl7yD3Io23c+t777WOBNT+/XkElOCNCfp8ogbB6JVfZz2MW6 +MeNqfemnotsMM12OvnebL3QGHDVFFdsQ9Gnt3LaEttHhC/Xg8FSHk41g0D/guaJc +XVhhgJBTW7hHl/vwCT8H/07z4ItGvILjttKCy89PqzxbodEoYNwNc/VSsQy9Eox/ +y2Z01EuiemcdvTMhZd6u8O5d5d0C52gbLmLeIkVmwOzOO7+9tJhEGcNJFGna4U5e +Phu2Hk8PiI4B4VpvlXMIxpOJQWXrxnhwNRf6SSKWCEZOopH82YUPmILMPAiIubjI +s2y2XwQXtt1Kj16RJriHngOn68gNAgMBAAGjUzBRMB0GA1UdDgQWBBS2JI9T0SRm +8h3qTzcr8Do6eLpdRTAfBgNVHSMEGDAWgBS2JI9T0SRm8h3qTzcr8Do6eLpdRTAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAojDNYMlZaZvjr2CQX +OBVSY6ZJjiKkI1c4Ihx1Dx7RqJeGYUnqwrzRU5HTlvAvLUHfFMIV9ahXeuRlpsJ5 +DY2kjTwtyTozF8IPPEwc8DQrFhuO2qaZOzvCbYWqO5uqx0TKxXN227xv85pHT2if +7IwhJwKInQg1d56heZ6Isz0tFysXeY7NJZ3iYgBg7PsBTeDiAPAW3XvXY2naSXui +vp12fnfcH7GF8QrcuCiqrgDfNASsfkONfCDBmOPJdLmxoGHcXxwORrGM2XQkPeAU +c/exZclzK5ke4rWkm0DoC/soHkCPkwE7LFcS0Uzvmyb5ORPSycBVS5mS13cz7YAy +L/wk +-----END CERTIFICATE----- diff --git a/src/test/ruby/x509/test_x509cert.rb b/src/test/ruby/x509/test_x509cert.rb index f4662e17..fac2a77d 100644 --- a/src/test/ruby/x509/test_x509cert.rb +++ b/src/test/ruby/x509/test_x509cert.rb @@ -15,6 +15,15 @@ def test_new assert_raise(OpenSSL::X509::CertificateError) { cert.public_key } end + def test_new_from_java_bytes # was historically supported through the StringHelper.readInput fallback + cert = File.read(File.expand_path('ca.crt', File.dirname(__FILE__))) + fact = java.security.cert.CertificateFactory.getInstance("X.509") + java_cert = fact.generateCertificate(java.io.ByteArrayInputStream.new(cert.to_java_bytes)) # X509Certificate + cert = OpenSSL::X509::Certificate.new(java_cert.getEncoded) # byte[] + assert_equal java_cert.getSerialNumber, cert.serial.to_java + assert_equal java_cert.getPublicKey, cert.public_key.to_java + end + def test_alt_name_extension cert = OpenSSL::X509::Certificate.new cert.add_extension OpenSSL::X509::Extension.new('subjectAltName', 'email:self@jruby.org, IP:127.0.0.1', false) @@ -74,18 +83,16 @@ def test_cert_extensions # JRUBY-3468 end def test_aki_extension_to_text - cert = create_self_signed_cert [ %w[CN localhost] ], __method__ + cert = create_self_signed_cert [ %w[CN localhost] ], __method__.to_s keyid = "97:39:9D:C3:FB:CD:BA:8F:54:0C:90:7B:46:3F:EA:D6:43:75:B1:CB" assert cert.extensions.size > 0 value = cert.extensions.last.value - # assert_equal "keyid:#{keyid}\nDirName:/CN=localhost\nserial:01\n", value - assert value.start_with?("keyid:#{keyid}\n") - assert value.end_with?("\nserial:01\n") + assert_equal "keyid:#{keyid}\nDirName:/CN=localhost\nserial:01\n", value end def create_self_signed_cert(cn, comment) # cert generation ripped from WEBrick - rsa = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048 + key = OpenSSL::PKey::RSA.new TEST_KEY_RSA2048 cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 1 @@ -94,7 +101,7 @@ def create_self_signed_cert(cn, comment) # cert generation ripped from WEBrick cert.issuer = name cert.not_before = Time.now cert.not_after = Time.now + (365*24*60*60) - cert.public_key = rsa.public_key + cert.public_key = key.public_key ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) ef.issuer_certificate = cert @@ -107,7 +114,7 @@ def create_self_signed_cert(cn, comment) # cert generation ripped from WEBrick ] aki = ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") cert.add_extension(aki) - cert.sign(rsa, OpenSSL::Digest::SHA1.new) + cert.sign(key, OpenSSL::Digest::SHA1.new) cert end @@ -125,8 +132,8 @@ def test_resolve_extensions ] now = Time.now - ca_cert = issue_cert(ca, rsa2048, 1, now, now + 3600, ca_exts, - nil, nil, OpenSSL::Digest::SHA1.new) + ca_cert = issue_cert(ca, rsa2048, 1, ca_exts, nil, nil, + not_before: now, not_after: now + 3600, digest: OpenSSL::Digest::SHA1.new) assert_equal 5, ca_cert.extensions.size @@ -134,11 +141,10 @@ def test_resolve_extensions assert_equal 5, cert.extensions.size # Java 6/7 seems to maintain same order but Java 8 does definitely not : - # TODO there must be something going on under - maybe not BC parsing ?!? - if self.class.java6? || self.class.java7? - assert_equal '97:39:9D:C3:FB:CD:BA:8F:54:0C:90:7B:46:3F:EA:D6:43:75:B1:CB', cert.extensions[2].value - assert_equal 'email:self@jruby.org, DNS:jruby.org', cert.extensions[4].value - end + #if self.class.java_version.last.to_i == 7 + # assert_equal '97:39:9D:C3:FB:CD:BA:8F:54:0C:90:7B:46:3F:EA:D6:43:75:B1:CB', cert.extensions[2].value + # assert_equal 'email:self@jruby.org, DNS:jruby.org', cert.extensions[4].value + #end exts = cert.extensions.dup @@ -174,8 +180,8 @@ def test_extensions ] now = Time.now - ca_cert = issue_cert(ca, rsa2048, 1, now, now + 3600, ca_exts, - nil, nil, OpenSSL::Digest::SHA1.new) + ca_cert = issue_cert(ca, rsa2048, 1, ca_exts, nil, nil, + not_before: now, not_after: now + 3600, digest: OpenSSL::Digest::SHA1.new) assert_equal 8, ca_cert.extensions.size ca_cert.extensions.each_with_index do |ext, i| @@ -216,7 +222,7 @@ def test_inspect_to_text dgst = OpenSSL::Digest::SHA1.new # NOTE: does it match MRI ?! - cert = issue_cert(subj, key, s, now, now + 3600, exts, nil, nil, dgst) + cert = issue_cert(subj, key, s, exts, nil, nil, not_before: now, not_after: now + 3600, digest: dgst) assert cert.inspect.start_with?('# e + # MRI: "X509_NAME_add_entry_by_txt: nested asn1 error" + assert e + end + end + + def test_hash_empty + name = OpenSSL::X509::Name.new + assert_equal 4003674586, name.hash + end + + def test_hash + name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] + assert_equal 3974220101, name.hash + end + + def test_hash_multiple_spaces_mixed_case + name = OpenSSL::X509::Name.new [['CN', 'foo bar'], ['DC', 'BAZ']] + name2 = OpenSSL::X509::Name.new [['CN', 'foo bar'], ['DC', 'baz']] + assert_equal 1941551332, name.hash + assert_equal 1941551332, name2.hash + end + + def test_hash_long_name + name = OpenSSL::X509::Name.new [['CN', 'a' * 255], ['DC', 'example']] + assert_equal 214469118, name.hash + end + + def test_hash_old + name = OpenSSL::X509::Name.new [['CN', 'nobody'], ['DC', 'example']] + assert_equal 1460400684, name.hash_old + name = OpenSSL::X509::Name.new([['CN', 'foo'], ['DC', 'bar']]) + assert_equal 3294068023, name.hash_old + end + + 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_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") + ) + + 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_integration + key = OpenSSL::PKey::RSA.new(4096) + + subject = "/C=FR/ST=IDF/L=PARIS/O=Company/CN=myhost.example" + + cert = OpenSSL::X509::Certificate.new + + fields = [] + OpenSSL::X509::Name.parse(subject).to_a.each do |field| + fields << [field[0], field[1], OpenSSL::ASN1::PRINTABLESTRING] + end + + subject_x509 = OpenSSL::X509::Name.new(fields) + + assert_equal '#', subject_x509.inspect + + cert.subject = cert.issuer = subject_x509 + + cert.not_before = Time.now + cert.not_after = Time.now + 365*24*60*60 + cert.public_key = key.public_key + cert.serial = 0x0 + cert.version = 2 + + ef = OpenSSL::X509::ExtensionFactory.new + ef.subject_certificate = ef.issuer_certificate = cert + + cert.add_extension ef.create_extension('basicConstraints', 'CA:FALSE', true) + cert.add_extension ef.create_extension('keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') + cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash') + cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always') + + cert.sign key, OpenSSL::Digest::SHA256.new + + asn1 = OpenSSL::ASN1.decode(cert.to_der) + + print_asn_strings(asn1) + end + + private + + def print_asn_strings(obj, depth = 0) + if obj.respond_to? :each + obj.each do |item| + print_asn_strings(item, depth + 1) + end + else + # printf("%-40s %s\n", obj.value, obj.class) + assert_equal OpenSSL::ASN1::PrintableString, obj.class if ( + obj.class.to_s.match(/String/) && obj.class != OpenSSL::ASN1::BitString + ) + end + nil + end + end \ No newline at end of file diff --git a/src/test/ruby/x509/test_x509req.rb b/src/test/ruby/x509/test_x509req.rb index c095a97d..14a1786b 100644 --- a/src/test/ruby/x509/test_x509req.rb +++ b/src/test/ruby/x509/test_x509req.rb @@ -2,6 +2,59 @@ class TestX509Request < TestCase + def setup! + @rsa1024 = Fixtures.pkey("rsa1024") + @rsa2048 = Fixtures.pkey("rsa2048") + @dsa256 = Fixtures.pkey("dsa256") + @dsa512 = Fixtures.pkey("dsa512") + @dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=GOTOU Yuuzou") + end + private :setup! + + def test_public_key; setup! + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA256')) + assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(@rsa1024.public_key.to_der, req.public_key.to_der) + + req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256')) + assert_equal(@dsa512.public_key.to_der, req.public_key.to_der) + req = OpenSSL::X509::Request.new(req.to_der) + assert_equal(@dsa512.public_key.to_der, req.public_key.to_der) + end + + def test_sign_and_verify_rsa_sha1; setup! + req = issue_csr(0, @dn, @rsa1024, OpenSSL::Digest.new('SHA1')) + assert_equal(true, req.verify(@rsa1024)) + assert_equal(false, req.verify(@rsa2048)) + assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) + assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) + # req.version = 1 + # assert_equal(false, req.verify(@rsa1024)) + #rescue OpenSSL::X509::RequestError # RHEL 9 disables SHA1 + end + + def test_sign_and_verify_rsa_md5; setup! + req = issue_csr(0, @dn, @rsa2048, OpenSSL::Digest.new('MD5')) + assert_equal(false, req.verify(@rsa1024)) + assert_equal(true, req.verify(@rsa2048)) + assert_equal(false, request_error_returns_false { req.verify(@dsa256) }) + assert_equal(false, request_error_returns_false { req.verify(@dsa512) }) + req.subject = OpenSSL::X509::Name.parse("/C=JP/CN=FooBar") + assert_equal(false, req.verify(@rsa2048)) + #rescue OpenSSL::X509::RequestError # RHEL7 disables MD5 + end + + def test_sign_and_verify_dsa; setup! + req = issue_csr(0, @dn, @dsa512, OpenSSL::Digest.new('SHA256')) + assert_equal(false, request_error_returns_false { req.verify(@rsa1024) }) + assert_equal(false, request_error_returns_false { req.verify(@rsa2048) }) + assert_equal(false, req.verify(@dsa256)) + assert_equal(true, req.verify(@dsa512)) + req.public_key = @rsa1024.public_key + assert_equal(false, req.verify(@dsa512)) + end + def test_csr_request_extensions key = OpenSSL::PKey::RSA.new(512) csr = OpenSSL::X509::Request.new @@ -18,6 +71,8 @@ def test_csr_request_extensions csr.sign(key, OpenSSL::Digest::SHA256.new) + assert_equal 'sha256WithRSAEncryption', csr.signature_algorithm + # The combination of the extreq and the stringification / revivification # is what triggers the bad behaviour in the extension. (Any extended # request type should do, but this matches my observed problems) @@ -28,9 +83,26 @@ def test_csr_request_extensions assert_equal 0, csr.version end + def test_csr_request_ec_key + key = OpenSSL::PKey::EC.generate('secp384r1') + + csr = OpenSSL::X509::Request.new + csr.public_key = key + csr.subject = OpenSSL::X509::Name.new([['CN', 'foo.bar.cat', OpenSSL::ASN1::UTF8STRING]]) + csr.version = 2 + + assert_equal 'NULL', csr.signature_algorithm + + csr.sign key, OpenSSL::Digest::SHA256.new # does not raise + + assert_equal 'ecdsa-with-SHA256', csr.signature_algorithm + + assert_true csr.verify(key) + end + def test_version csr = OpenSSL::X509::Request.new - assert_equal 0, csr.version + assert_equal -1, csr.version req = OpenSSL::X509::Request.new req.version = 1 @@ -58,6 +130,23 @@ def test_to_der_new_from_der; require 'base64' OpenSSL::X509::Request.new(decoded) #=> OpenSSL::X509::RequestError: invalid certificate request data end + private + + def issue_csr(ver, dn, key, digest) + req = OpenSSL::X509::Request.new + req.version = ver + req.subject = dn + req.public_key = key.public_key + req.sign(key, digest) + req + end + + def request_error_returns_false + yield + rescue OpenSSL::X509::RequestError + false + end + TEST_KEY_RSA1024 = <<-_end_of_pem_ -----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDLwsSw1ECnPtT+PkOgHhcGA71nwC2/nL85VBGnRqDxOqjVh7Cx diff --git a/src/test/ruby/x509/test_x509store.rb b/src/test/ruby/x509/test_x509store.rb index b8a292c7..44fa20a2 100644 --- a/src/test/ruby/x509/test_x509store.rb +++ b/src/test/ruby/x509/test_x509store.rb @@ -43,7 +43,7 @@ def test_store_location_with_java_truststore assert ! store.verify(@cert) store.set_default_paths - puts @cert.inspect if $VERBOSE + #puts @cert.inspect if $VERBOSE #puts @cert.to_java java.security.cert.X509Certificate verified = store.verify(@cert) @@ -74,7 +74,7 @@ def test_add_file_to_store_with_custom_cert_file store.add_file @pem cert = OpenSSL::X509::Certificate.new(File.read(@pem)) - p cert if $VERBOSE + #p cert if $VERBOSE verified = store.verify(cert) assert verified, "verification failed for cert: #{cert.inspect} - #{store.inspect}" @@ -144,12 +144,13 @@ def test_add_cert_concurrently cert_store = OpenSSL::X509::Store.new assert cert_store.add_cert(root_ca) == cert_store - begin + # NOTE: logic reverted in JOSSL 0.11.0 to match C-OpenSSL (just adds certificates wout checks) + #begin cert_store.add_cert(root_ca) - fail 'added same cert twice' - rescue OpenSSL::X509::StoreError => e - assert_equal 'cert already in hash table', e.message - end + #fail 'added same cert twice' + #rescue OpenSSL::X509::StoreError => e + #assert_equal 'cert already in hash table', e.message + #end end def test_adding_pem_to_store_like_rubygems @@ -174,8 +175,8 @@ def test_adding_pem_to_store_like_rubygems end if defined?(JRUBY_VERSION) && Gem::Version.create(JRUBY_VERSION) >= Gem::Version.create('9.1.17.0') def test_verify - @rsa1024 = OpenSSL::PKey::RSA.new SSLTestHelper::TEST_KEY_RSA1024 # OpenSSL::TestUtils::TEST_KEY_RSA1024 - @rsa2048 = OpenSSL::PKey::RSA.new SSLTestHelper::TEST_KEY_RSA2048 # OpenSSL::TestUtils::TEST_KEY_RSA2048 + @rsa1024 = OpenSSL::PKey::RSA.new SSLTestHelper::TEST_KEY_RSA1 # OpenSSL::TestUtils::TEST_KEY_RSA1024 + @rsa2048 = OpenSSL::PKey::RSA.new SSLTestHelper::TEST_KEY_RSA2 # OpenSSL::TestUtils::TEST_KEY_RSA2048 @dsa256 = OpenSSL::PKey::DSA.new SSLTestHelper::TEST_KEY_DSA256 # OpenSSL::TestUtils::TEST_KEY_DSA256 @dsa512 = OpenSSL::PKey::DSA.new SSLTestHelper::TEST_KEY_DSA512 # OpenSSL::TestUtils::TEST_KEY_DSA512 @ca1 = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA1") @@ -191,18 +192,12 @@ def test_verify ee_exts = [ ["keyUsage","keyEncipherment,digitalSignature",true], ] - ca1_cert = issue_cert(@ca1, @rsa2048, 1, now, now+3600, ca_exts, - nil, nil, OpenSSL::Digest::SHA1.new) - ca2_cert = issue_cert(@ca2, @rsa1024, 2, now, now+1800, ca_exts, - ca1_cert, @rsa2048, OpenSSL::Digest::SHA1.new) - ee1_cert = issue_cert(@ee1, @dsa256, 10, now, now+1800, ee_exts, - ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) - ee2_cert = issue_cert(@ee2, @dsa512, 20, now, now+1800, ee_exts, - ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) - ee3_cert = issue_cert(@ee2, @dsa512, 30, now-100, now-1, ee_exts, - ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) - ee4_cert = issue_cert(@ee2, @dsa512, 40, now+1000, now+2000, ee_exts, - ca2_cert, @rsa1024, OpenSSL::Digest::SHA1.new) + ca1_cert = issue_cert(@ca1, @rsa2048, 1, ca_exts, nil, nil, not_before: now, not_after: now + 3600) + ca2_cert = issue_cert(@ca2, @rsa1024, 2, ca_exts, ca1_cert, @rsa2048, not_before: now, not_after: now + 1800) + ee1_cert = issue_cert(@ee1, @dsa256, 10, ee_exts, ca2_cert, @rsa1024, not_before: now, not_after: now + 1800) + ee2_cert = issue_cert(@ee2, @dsa512, 20, ee_exts, ca2_cert, @rsa1024, not_before: now, not_after: now + 1800) + ee3_cert = issue_cert(@ee2, @dsa512, 30, ee_exts, ca2_cert, @rsa1024, not_before: now - 100, not_after: now - 1) + ee4_cert = issue_cert(@ee2, @dsa512, 40, ee_exts, ca2_cert, @rsa1024, not_before: now + 1000, not_after: now + 2000) revoke_info = [] crl1 = issue_crl(revoke_info, 1, now, now+1800, [], @@ -235,7 +230,17 @@ def test_verify assert_not_equal(OpenSSL::X509::V_OK, store.error) store.add_cert(ca1_cert) - assert_equal(true, store.verify(ca2_cert)) + verify = store.verify(ca1_cert) + # TODO only works when cert_self_signed is reduced to do a EXFLAG_SI instead of EXFLAG_SS + assert_equal ["/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + assert_equal(true, verify) + + verify = store.verify(ca2_cert) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA2", "/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + assert_equal(true, verify) + assert_equal(OpenSSL::X509::V_OK, store.error) assert_equal("ok", store.error_string) chain = store.chain @@ -306,9 +311,18 @@ def test_verify store.add_crl(crl1) # revoke no cert store.add_crl(crl2) # revoke ee2_cert assert_equal(true, store.verify(ca1_cert)) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + 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])) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA2", "/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + + verify = store.verify(ee1_cert, [ca2_cert]) + assert_equal(true, verify) + + verify = store.verify(ee2_cert, [ca2_cert]) + assert_equal(false, verify) store = OpenSSL::X509::Store.new store.purpose = OpenSSL::X509::PURPOSE_ANY @@ -316,15 +330,29 @@ def test_verify store.add_cert(ca1_cert) store.add_crl(crl1_2) # revoke ca2_cert store.add_crl(crl2) # revoke ee2_cert - assert_equal(true, store.verify(ca1_cert)) - assert_equal(false, store.verify(ca2_cert)) + + verify = store.verify(ca1_cert) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + assert_equal(true, verify) + + verify = store.verify(ca2_cert) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA2", "/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + assert_equal(false, verify) + assert_equal(true, store.verify(ee1_cert, [ca2_cert]), "This test is expected to be success with OpenSSL 0.9.7c or later.") assert_equal(false, store.verify(ee2_cert, [ca2_cert])) - store.flags = - OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL - assert_equal(true, store.verify(ca1_cert)) + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK | OpenSSL::X509::V_FLAG_CRL_CHECK_ALL + + verify = store.verify(ca1_cert) + assert_equal ["/DC=org/DC=ruby-lang/CN=CA1"], + store.chain.map { |cert| cert.subject.to_s } + puts "verify(ca1_cert) #{verify} - store.error: #{store.error} (#{store.error_string})" + assert_equal(true, verify) + 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])) @@ -344,4 +372,61 @@ def test_verify assert_equal(false, store.verify(ee2_cert)) end + def test_verify_same_subject_ca + + puts JOpenSSL::VERSION if defined? JRUBY_VERSION + + @rsa1 = OpenSSL::PKey::RSA.generate 2048 + @rsa2 = OpenSSL::PKey::RSA.generate 2048 + @rsa3 = OpenSSL::PKey::RSA.generate 2048 + @rsa4 = OpenSSL::PKey::RSA.generate 2048 + @dsa1 = OpenSSL::PKey::DSA.generate 512 + @dsa2 = OpenSSL::PKey::DSA.generate 512 + @ca_same = OpenSSL::X509::Name.parse("/DC=com/DC=same-name/CN=CA") + @ca_other = OpenSSL::X509::Name.parse("/DC=co/DC=anotherOne/CN=CA") + @ee1 = OpenSSL::X509::Name.parse("/DC=com/DC=example/CN=ServerCert1") + @ee2 = OpenSSL::X509::Name.parse("/DC=com/DC=example/CN=ServerCert2") + @ee4 = OpenSSL::X509::Name.parse("/DC=com/DC=example/CN=ServerCert4") + + now = Time.at(Time.now.to_i) + not_before = now - 365 * 24 * 60 * 60 + not_after = now + 24 * 60 * 60 + ca_exts1 = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","cRLSign,keyCertSign",true], + ] + ca_exts2 = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","keyCertSign",true], + ] + ee_exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ] + ca1_cert = issue_cert(@ca_same, @rsa1, 1, ca_exts1, nil, nil, not_before: not_before, not_after: now - 60 * 60) + ca2_cert = issue_cert(@ca_same, @rsa2, 2, ca_exts2, nil, nil, not_before: not_before, not_after: not_after) + ca3_cert = issue_cert(@ca_other, @rsa3, 3, ca_exts1, nil, nil, not_before: not_before, not_after: not_after) + ca4_cert = issue_cert(@ca_same, @rsa4, 4, ca_exts1, nil, nil, not_before: not_before, not_after: not_after) + ee1_cert = issue_cert(@ee1, @dsa1, 10, ee_exts, ca1_cert, @rsa1, not_before: now - 60, not_after: now + 1800) + ee2_cert = issue_cert(@ee2, @dsa2, 20, ee_exts, ca2_cert, @rsa2, not_before: now - 60, not_after: now + 1800) + ee4_cert = issue_cert(@ee4, @dsa2, 20, ee_exts, ca4_cert, @rsa4, not_before: now - 60, not_after: now + 1800) + + cert_store = OpenSSL::X509::Store.new + cert_store.add_cert ca1_cert + cert_store.add_cert ca2_cert + cert_store.add_cert ca3_cert + cert_store.add_cert ca4_cert + + ok = cert_store.verify(ee1_cert) + assert_equal 'certificate signature failure', cert_store.error_string + assert_equal false, ok + + ok = cert_store.verify(ee2_cert) + assert_equal 'ok', cert_store.error_string + assert_equal true, ok + + ok = cert_store.verify(ee4_cert) + assert_equal 'certificate signature failure', cert_store.error_string + assert_equal false, ok # OpenSSL 1.1.1 behavior + end + end