diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e98354e80..c9ddfbc41 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,8 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby-truffleruby - min_version: 2.6 + min_version: 2.7 + test: needs: ruby-versions name: >- @@ -20,12 +21,8 @@ jobs: os: [ ubuntu-22.04, ubuntu-20.04, macos-latest, windows-latest ] ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} exclude: - # uses non-standard MSYS2 OpenSSL 3 package - - { os: windows-latest, ruby: head } - { os: windows-latest, ruby: truffleruby } - { os: windows-latest, ruby: truffleruby-head } - - { os: macos-latest, ruby: truffleruby } - - { os: ubuntu-20.04, ruby: truffleruby } include: - { os: windows-latest, ruby: ucrt } - { os: windows-latest, ruby: mswin } @@ -38,44 +35,43 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - - - name: depends - run: bundle install + bundler-cache: true # `bundle install` and cache # Enable the verbose option in mkmf.rb to print the compiling commands. - name: enable mkmf verbose run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV if: runner.os == 'Linux' || runner.os == 'macOS' - - name: set flags to check compiler warnings. + - name: set flags to check compiler warnings run: echo "RUBY_OPENSSL_EXTCFLAGS=-Werror" >> $GITHUB_ENV if: ${{ !matrix.skip-warnings }} - - name: compile - run: rake compile + - name: rake compile + run: bundle exec rake compile - - name: test - run: rake test TESTOPTS="-v --no-show-detail-immediately" + - name: rake test + run: bundle exec rake test TESTOPTS="-v --no-show-detail-immediately" timeout-minutes: 5 test-openssls: name: >- - ${{ matrix.openssl }} ${{ matrix.name-extra || '' }} - runs-on: ${{ matrix.os }} + ${{ matrix.openssl }} ${{ matrix.name-extra }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - os: [ ubuntu-latest ] - ruby: [ "3.0" ] + name-extra: [ '' ] openssl: - # https://www.openssl.org/source/ + # https://openssl-library.org/source/ - openssl-1.0.2u # EOL - openssl-1.1.0l # EOL - - openssl-1.1.1w # EOL - - openssl-3.0.13 - - openssl-3.1.5 - - openssl-3.2.1 - - openssl-3.3.0 + - openssl-1.1.1w # EOL 2023-09-11, still used by RHEL 8 and Ubuntu 20.04 + - openssl-3.0.15 # Supported until 2026-09-07 + - openssl-3.1.7 # Supported until 2025-03-14 + - openssl-3.2.3 # Supported until 2025-11-23 + - openssl-3.3.2 # Supported until 2026-04-09 + - openssl-3.4.0 # Supported until 2026-10-22 + - openssl-master # http://www.libressl.org/releases.html - libressl-3.1.5 # EOL - libressl-3.2.7 # EOL @@ -84,64 +80,83 @@ jobs: - libressl-3.5.3 # EOL - libressl-3.6.3 # EOL - libressl-3.7.3 # EOL - - libressl-3.8.4 - - libressl-3.9.1 + - libressl-3.8.4 # EOL 2024-10-16 + - libressl-3.9.2 # Supported until 2025-04-05 + - libressl-4.0.0 steps: - name: repo checkout uses: actions/checkout@v4 - - name: prepare openssl + - id: cache-openssl + uses: actions/cache@v4 + with: + path: ~/openssl + key: openssl-${{ runner.os }}-${{ matrix.openssl }}-${{ matrix.append-configure || 'default' }} + if: matrix.openssl != 'openssl-master' && matrix.openssl != 'libressl-master' + + - name: Compile OpenSSL library + if: steps.cache-openssl.outputs.cache-hit != 'true' run: | # Enable Bash debugging option temporarily for debugging use. set -x mkdir -p tmp/build-openssl && cd tmp/build-openssl case ${{ matrix.openssl }} in - openssl-*) - if [ -z "${{ matrix.git }}" ]; then - curl -OL https://openssl.org/source/${{ matrix.openssl }}.tar.gz - tar xf ${{ matrix.openssl }}.tar.gz && cd ${{ matrix.openssl }} - else - git clone -b ${{ matrix.branch }} --depth 1 ${{ matrix.git }} ${{ matrix.openssl }} - cd ${{ matrix.openssl }} - # Log the commit hash. - echo "Git commit: $(git rev-parse HEAD)" - fi + openssl-1.*) + OPENSSL_COMMIT=$(echo ${{ matrix.openssl }} | sed -e 's/^openssl-/OpenSSL_/' | sed -e 's/\./_/g') + git clone -b $OPENSSL_COMMIT --depth 1 https://github.com/openssl/openssl.git . + echo "Git commit: $(git rev-parse HEAD)" # shared is required for 1.0.x. - ./Configure --prefix=$HOME/.openssl/${{ matrix.openssl }} --libdir=lib \ - shared linux-x86_64 ${{ matrix.append-configure }} - make depend + ./Configure --prefix=$HOME/openssl --libdir=lib shared linux-x86_64 + make depend && make -j4 && make install_sw + ;; + openssl-*) + OPENSSL_COMMIT=${{ matrix.openssl == 'openssl-master' && 'master' || matrix.openssl }} + git clone -b $OPENSSL_COMMIT --depth 1 https://github.com/openssl/openssl.git . + echo "Git commit: $(git rev-parse HEAD)" + ./Configure --prefix=$HOME/openssl --libdir=lib enable-fips ${{ matrix.append-configure }} + make -j4 && make install_sw && make install_fips ;; libressl-*) - curl -OL https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/${{ matrix.openssl }}.tar.gz - tar xf ${{ matrix.openssl }}.tar.gz && cd ${{ matrix.openssl }} - ./configure --prefix=$HOME/.openssl/${{ matrix.openssl }} + curl -L https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/${{ matrix.openssl }}.tar.gz | \ + tar xzf - --strip-components=1 + ./configure --prefix=$HOME/openssl + make -j4 && make install ;; *) false ;; esac - make -j4 - make install_sw - name: load ruby uses: ruby/setup-ruby@v1 with: - ruby-version: ${{ matrix.ruby }} - - - name: depends - run: bundle install + ruby-version: '3.0' + bundler-cache: true - name: enable mkmf verbose run: echo "MAKEFLAGS=V=1" >> $GITHUB_ENV - if: runner.os == 'Linux' || runner.os == 'macOS' - - name: set flags to check compiler warnings. + - name: set flags to check compiler warnings run: echo "RUBY_OPENSSL_EXTCFLAGS=-Werror" >> $GITHUB_ENV if: ${{ !matrix.skip-warnings }} - - name: compile - run: rake compile -- --with-openssl-dir=$HOME/.openssl/${{ matrix.openssl }} + - name: rake compile + run: bundle exec rake compile -- --with-openssl-dir=$HOME/openssl + + - name: setup OpenSSL config file for fips + run: | + sed -e "s|OPENSSL_DIR|$HOME/openssl|" tool/openssl_fips.cnf.tmpl > tmp/openssl_fips.cnf + echo "OPENSSL_CONF=$(pwd)/tmp/openssl_fips.cnf" >> $GITHUB_ENV + if: matrix.fips-enabled + + - name: rake test + run: bundle exec rake test TESTOPTS="-v --no-show-detail-immediately" + timeout-minutes: 5 + if: ${{ !matrix.fips-enabled }} - - name: test - run: rake test TESTOPTS="-v --no-show-detail-immediately" + # Run only the passing tests on the FIPS module as a temporary workaround. + # TODO Fix other tests, and run all the tests on FIPS module. + - name: rake test_fips + run: bundle exec rake test_fips TESTOPTS="-v --no-show-detail-immediately" timeout-minutes: 5 + if: matrix.fips-enabled diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 71c452c88..f26a4759c 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -1298,30 +1298,6 @@ ossl_asn1obj_get_ln(VALUE self) return ret; } -/* - * call-seq: - * oid == other_oid => true or false - * - * Returns +true+ if _other_oid_ is the same as _oid_ - */ -static VALUE -ossl_asn1obj_eq(VALUE self, VALUE other) -{ - VALUE valSelf, valOther; - int nidSelf, nidOther; - - valSelf = ossl_asn1_get_value(self); - valOther = ossl_asn1_get_value(other); - - if ((nidSelf = OBJ_txt2nid(StringValueCStr(valSelf))) == NID_undef) - ossl_raise(eASN1Error, "OBJ_txt2nid"); - - if ((nidOther = OBJ_txt2nid(StringValueCStr(valOther))) == NID_undef) - ossl_raise(eASN1Error, "OBJ_txt2nid"); - - return nidSelf == nidOther ? Qtrue : Qfalse; -} - static VALUE asn1obj_get_oid_i(VALUE vobj) { @@ -1366,6 +1342,25 @@ ossl_asn1obj_get_oid(VALUE self) return str; } +/* + * call-seq: + * oid == other_oid => true or false + * + * Returns +true+ if _other_oid_ is the same as _oid_. + */ +static VALUE +ossl_asn1obj_eq(VALUE self, VALUE other) +{ + VALUE oid1, oid2; + + if (!rb_obj_is_kind_of(other, cASN1ObjectId)) + return Qfalse; + + oid1 = ossl_asn1obj_get_oid(self); + oid2 = ossl_asn1obj_get_oid(other); + return rb_str_equal(oid1, oid2); +} + #define OSSL_ASN1_IMPL_FACTORY_METHOD(klass) \ static VALUE ossl_asn1_##klass(int argc, VALUE *argv, VALUE self)\ { return rb_funcall3(cASN1##klass, rb_intern("new"), argc, argv); } diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index c8f7924e0..59813dc1d 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -167,8 +167,10 @@ ossl_pkcs7_s_read_smime(VALUE klass, VALUE arg) BIO_free(in); if (!pkcs7) ossl_raise(ePKCS7Error, "Could not parse the PKCS7"); - if (!pkcs7->d.ptr) + if (!pkcs7->d.ptr) { + PKCS7_free(pkcs7); ossl_raise(ePKCS7Error, "No content in PKCS7"); + } data = out ? ossl_membio2str(out) : Qnil; SetPKCS7(ret, pkcs7); @@ -350,8 +352,10 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self) BIO_free(in); if (!p7) ossl_raise(rb_eArgError, "Could not parse the PKCS7"); - if (!p7->d.ptr) + if (!p7->d.ptr) { + PKCS7_free(p7); ossl_raise(rb_eArgError, "No content in PKCS7"); + } RTYPEDDATA_DATA(self) = p7; PKCS7_free(p7_orig); diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb index f973f4f4d..2fda0d5e2 100644 --- a/lib/openssl/x509.rb +++ b/lib/openssl/x509.rb @@ -122,8 +122,8 @@ 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 + # distribution points extension, as described in RFC 5280 Section + # 4.2.1.13. # # Returns an array of strings or nil or raises ASN1::ASN1Error. def crl_uris @@ -135,19 +135,19 @@ def crl_uris raise ASN1::ASN1Error, "invalid extension" end - crl_uris = cdp_asn1.map do |crl_distribution_point| + crl_uris = cdp_asn1.flat_map do |crl_distribution_point| distribution_point = crl_distribution_point.value.find do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 end full_name = distribution_point&.value&.find do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 end - full_name&.value&.find do |v| + full_name&.value&.select do |v| v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier end end - crl_uris&.map(&:value) + crl_uris.empty? ? nil : crl_uris.map(&:value) end end diff --git a/test/openssl/test_asn1.rb b/test/openssl/test_asn1.rb index 7b1722e5d..354b58789 100644 --- a/test/openssl/test_asn1.rb +++ b/test/openssl/test_asn1.rb @@ -326,7 +326,9 @@ def test_object_identifier oid = (0...100).to_a.join(".").b obj = OpenSSL::ASN1::ObjectId.new(oid) assert_equal oid, obj.oid + end + def test_object_identifier_equality aki = [ OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier"), OpenSSL::ASN1::ObjectId.new("X509v3 Authority Key Identifier"), @@ -341,17 +343,22 @@ def test_object_identifier aki.each do |a| aki.each do |b| - assert a == b + assert_equal true, a == b end ski.each do |b| - refute a == b + assert_equal false, a == b end end - assert_raise(TypeError) { - OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil - } + obj1 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10") + obj2 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.10") + obj3 = OpenSSL::ASN1::ObjectId.new("1.2.34.56789.11") + omit "OID 1.2.34.56789.10 is registered" if obj1.sn + assert_equal true, obj1 == obj2 + assert_equal false, obj1 == obj3 + + assert_equal false, OpenSSL::ASN1::ObjectId.new("authorityKeyIdentifier") == nil end def test_sequence diff --git a/test/openssl/test_x509cert.rb b/test/openssl/test_x509cert.rb index 64805504d..e39b6b6a4 100644 --- a/test/openssl/test_x509cert.rb +++ b/test/openssl/test_x509cert.rb @@ -68,7 +68,7 @@ def test_validity assert_equal(now.getutc, cert.not_after) end - def test_extension + def test_extension_factory ca_exts = [ ["basicConstraints","CA:TRUE",true], ["keyUsage","keyCertSign, cRLSign",true], @@ -76,9 +76,6 @@ def test_extension ["authorityKeyIdentifier","issuer:always,keyid:always",false], ] ca_cert = issue_cert(@ca, @rsa2048, 1, ca_exts, nil, nil) - keyid = get_subject_key_id(ca_cert.to_der, hex: false) - assert_equal keyid, ca_cert.authority_key_identifier - assert_equal keyid, ca_cert.subject_key_identifier ca_cert.extensions.each_with_index{|ext, i| assert_equal(ca_exts[i].first, ext.oid) assert_equal(ca_exts[i].last, ext.critical?) @@ -90,7 +87,6 @@ def test_extension ["authorityKeyIdentifier","issuer:always,keyid:always",false], ["extendedKeyUsage","clientAuth, emailProtection, codeSigning",false], ["subjectAltName","email:ee1@ruby-lang.org",false], - ["authorityInfoAccess","caIssuers;URI:http://www.example.com/caIssuers,OCSP;URI:http://www.example.com/ocsp",false], ] ee1_cert = issue_cert(@ee1, @rsa1024, 2, ee1_exts, ca_cert, @rsa2048) assert_equal(ca_cert.subject.to_der, ee1_cert.issuer.to_der) @@ -98,15 +94,54 @@ def test_extension assert_equal(ee1_exts[i].first, ext.oid) assert_equal(ee1_exts[i].last, ext.critical?) } - assert_nil(ee1_cert.crl_uris) + end + + def test_akiski + ca_cert = generate_cert(@ca, @rsa2048, 4, nil) + ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ca_cert) + ca_cert.add_extension( + ef.create_extension("subjectKeyIdentifier", "hash", false)) + ca_cert.add_extension( + ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) + ca_cert.sign(@rsa2048, "sha256") + + ca_keyid = get_subject_key_id(ca_cert.to_der, hex: false) + assert_equal ca_keyid, ca_cert.authority_key_identifier + assert_equal ca_keyid, ca_cert.subject_key_identifier + + ee_cert = generate_cert(@ee1, Fixtures.pkey("p256"), 5, ca_cert) + ef = OpenSSL::X509::ExtensionFactory.new(ca_cert, ee_cert) + ee_cert.add_extension( + ef.create_extension("subjectKeyIdentifier", "hash", false)) + ee_cert.add_extension( + ef.create_extension("authorityKeyIdentifier", "issuer:always,keyid:always", false)) + ee_cert.sign(@rsa2048, "sha256") + + ee_keyid = get_subject_key_id(ee_cert.to_der, hex: false) + assert_equal ca_keyid, ee_cert.authority_key_identifier + assert_equal ee_keyid, ee_cert.subject_key_identifier + end + + def test_akiski_missing + cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + assert_nil(cert.authority_key_identifier) + assert_nil(cert.subject_key_identifier) + end + + def test_crl_uris_no_crl_distribution_points + cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + assert_nil(cert.crl_uris) + end + def test_crl_uris + # Multiple DistributionPoint contains a single general name each ef = OpenSSL::X509::ExtensionFactory.new ef.config = OpenSSL::Config.parse(<<~_cnf_) [crlDistPts] URI.1 = http://www.example.com/crl URI.2 = ldap://ldap.example.com/cn=ca?certificateRevocationList;binary _cnf_ - cdp_cert = generate_cert(@ee1, @rsa1024, 3, ca_cert) + cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) ef.subject_certificate = cdp_cert cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "@crlDistPts")) cdp_cert.sign(@rsa2048, "sha256") @@ -114,9 +149,50 @@ def test_extension ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], cdp_cert.crl_uris ) + end + def test_crl_uris_multiple_general_names + # Single DistributionPoint contains multiple general names of type URI ef = OpenSSL::X509::ExtensionFactory.new - aia_cert = generate_cert(@ee1, @rsa1024, 4, ca_cert) + ef.config = OpenSSL::Config.parse(<<~_cnf_) + [crlDistPts_section] + fullname = URI:http://www.example.com/crl, URI:ldap://ldap.example.com/cn=ca?certificateRevocationList;binary + _cnf_ + cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) + ef.subject_certificate = cdp_cert + cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) + cdp_cert.sign(@rsa2048, "sha256") + assert_equal( + ["http://www.example.com/crl", "ldap://ldap.example.com/cn=ca?certificateRevocationList;binary"], + cdp_cert.crl_uris + ) + end + + def test_crl_uris_no_uris + # The only DistributionPointName is a directoryName + ef = OpenSSL::X509::ExtensionFactory.new + ef.config = OpenSSL::Config.parse(<<~_cnf_) + [crlDistPts_section] + fullname = dirName:dirname_section + [dirname_section] + CN = dirname + _cnf_ + cdp_cert = generate_cert(@ee1, @rsa2048, 3, nil) + ef.subject_certificate = cdp_cert + cdp_cert.add_extension(ef.create_extension("crlDistributionPoints", "crlDistPts_section")) + cdp_cert.sign(@rsa2048, "sha256") + assert_nil(cdp_cert.crl_uris) + end + + def test_aia_missing + cert = issue_cert(@ee1, @rsa2048, 1, [], nil, nil) + assert_nil(cert.ca_issuer_uris) + assert_nil(cert.ocsp_uris) + end + + def test_aia + ef = OpenSSL::X509::ExtensionFactory.new + aia_cert = generate_cert(@ee1, @rsa2048, 4, nil) ef.subject_certificate = aia_cert aia_cert.add_extension( ef.create_extension( @@ -137,13 +213,6 @@ def test_extension ["http://www.example.com/ocsp", "ldap://ldap.example.com/cn=ca?authorityInfoAccessOcsp;binary"], aia_cert.ocsp_uris ) - - no_exts_cert = issue_cert(@ca, @rsa2048, 5, [], nil, nil) - assert_equal nil, no_exts_cert.authority_key_identifier - assert_equal nil, no_exts_cert.subject_key_identifier - assert_equal nil, no_exts_cert.crl_uris - assert_equal nil, no_exts_cert.ca_issuer_uris - assert_equal nil, no_exts_cert.ocsp_uris end def test_invalid_extension