Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle empty signed data in PKCS7 #690

Closed
wants to merge 227 commits into from

Conversation

jeremyevans
Copy link
Contributor

This will have certificates and crls return nil instead of segfaulting.

Fixes [Bug #19974]

kmdz1 and others added 30 commits February 1, 2022 18:12
…along with some unit tests (ruby#493)

Add OpenSSL::SSL::SSLContext#ciphersuites= method along with unit tests.
I use ruby.git's tool/sync_default_gems.rb and simple git cherry-pick
instead nowadays.

These tasks actually don't correctly work because they use outdated
path references and expect ruby.git to be using git-svn, which is not
the case anymore.

Reported-by: rkoster <hi@rkoster.dev>
Fixes: ruby#495
Install openssl with vcpkg on mswin
LibreSSL 3.5 switched the cipher naming to match OpenSSL.
The + tag can only be used for single words. For multiple words the <tt>
tag has to be used.
Use SHA256 for OCSP BasicResponse and Request
[CI] add Ubuntu-22.04 and update mswin, all are OpenSSL 3
ignore pkgconfig when openssl-dir option is specified
While building with a custom build of OpenSSL, I noticed in mkmf.log
that all the feature detection checks are done using a program lacking
an OpenSSL header include. `mkmf` retries using a fallback program when
this fails, but that means all the `have_func` calls compile twice when
compiling once should suffice. Example log without this commit:

    have_func: checking for X509_STORE_CTX_get0_cert()... -------------------- yes

    DYLD_FALLBACK_LIBRARY_PATH=.:../.. "clang -o conftest ...
    conftest.c:14:57: error: use of undeclared identifier 'X509_STORE_CTX_get0_cert'
    int t(void) { void ((*volatile p)()); p = (void ((*)()))X509_STORE_CTX_get0_cert; return !p; }
                                                            ^
    1 error generated.
    checked program was:
    /* begin */
     1: #include "ruby.h"
     2:
     3: /*top*/
     4: extern int t(void);
     5: int main(int argc, char **argv)
     6: {
     7:   if (argc > 1000000) {
     8:     int (* volatile tp)(void)=(int (*)(void))&t;
     9:     printf("%d", (*tp)());
    10:   }
    11:
    12:   return !!argv[argc];
    13: }
    14: int t(void) { void ((*volatile p)()); p = (void ((*)()))X509_STORE_CTX_get0_cert; return !p; }
    /* end */

    DYLD_FALLBACK_LIBRARY_PATH=.:../.. "clang -o conftest ...
    checked program was:
    /* begin */
     1: #include "ruby.h"
     2:
     3: /*top*/
     4: extern int t(void);
     5: int main(int argc, char **argv)
     6: {
     7:   if (argc > 1000000) {
     8:     int (* volatile tp)(void)=(int (*)(void))&t;
     9:     printf("%d", (*tp)());
    10:   }
    11:
    12:   return !!argv[argc];
    13: }
    14: extern void X509_STORE_CTX_get0_cert();
    15: int t(void) { X509_STORE_CTX_get0_cert(); return 0; }
    /* end */

The second compilation succeeds.

Specify the header for each checked function.
It does not raise an error when setting an invalid value to SSLContext
ciphers on Ubuntu 18.04.
Skip a new test when old OpenSSL
X509_STORE_get_ex_new_index() is a macro, so passing just its name to
have_func() doesn't detect it. Pass an example call instead.

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Check for OpenSSL functions in headers
[CI] test.yml - test-openssls - use 1.1.1q, 3.0.5
[CI] TestHMAC#test_dup - remove 'pend' for OpenSSL 3
Add OpenSSL::SSL::SSLSocket#export_keying_material to support RFC 5705
junaruga and others added 13 commits September 20, 2023 14:48
Run the test with `assert_separately` for the `false` value of the
`OpenSSL.fips_mode` not to affect other tests.
* Split the test in the FIPS case as another test.
* test/openssl/utils.rb: Add omit_on_fips and omit_on_non_fips methods.
…ns/checkout-4

Bump actions/checkout from 3 to 4
 * Reword the description in README for more clarity.

 * Add a compatibility matrix of our stable branches and explain the
   maintenance policy.

 * Remove the obsolete paragraph for how to use the gem in Ruby 2.3,
   which is no longer supported.
Exact checks with `assert_include`
Where `assert_match` converts string matcher argument to regexp first
with escaping, `assert_include` does the same thing simpler.
Exact checks with `assert_include`
CI: Upgrade OpenSSL and LibreSSL versions.
Copy link

@ryanseys ryanseys left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍🏼

@rhenium
Copy link
Member

rhenium commented Oct 27, 2023

I think we should reject such input in PKCS7.new and PKCS7.read_smime. Many OpenSSL functions don't correctly handle this, and it doesn't make sense anyway. Currently, #add_certificate, #data=, etc. will also segfault with this PKCS7 object:

$ ruby -ropenssl -e'p OpenSSL::PKCS7.new("MAsGCSqGSIb3DQEHAg==".unpack1("m")).data="a"'
[...]
/usr/lib64/libcrypto.so.3(PKCS7_set_content+0x84) [0x7fc3522d6794] ../openssl-3.1.3/crypto/pkcs7/pk7_lib.c:90
/usr/lib64/libcrypto.so.3(PKCS7_set_content) (null):0
/usr/lib64/libcrypto.so.3(PKCS7_content_new+0x43) [0x7fc3522d69c3] ../openssl-3.1.3/crypto/pkcs7/pk7_lib.c:74
/usr/lib64/libcrypto.so.3(PKCS7_content_new) (null):0
[...]

Apparently, this is technically a valid PKCS#7 structure (https://datatracker.ietf.org/doc/html/rfc2315#section-7):

   ContentInfo ::= SEQUENCE {
     contentType ContentType,
     content
       [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL }

but the OPTIONAL was meant to be used by a ContentInfo embedded in another ContentInfo, for example, in a detached S/MIME signature. I don't think the outermost ContentInfo is supposed to ever omit content.

FWIW, IETF's successor of PKCS#7, CMS (https://datatracker.ietf.org/doc/html/rfc2630#section-3) defines a separate structure EncapsulatedContentInfo for this use case and disallows ContentInfo to omit content.

@rhenium rhenium changed the base branch from master to maint-3.0 October 27, 2023 07:40
@rhenium
Copy link
Member

rhenium commented Oct 27, 2023

This is not a new bug. I changed the base branch to the oldest supported one (gem 3.0/Ruby 3.1).

@jeremyevans
Copy link
Contributor Author

I updated the PR to raise an error in #initialize if there should be signed data but there is not.

@rhenium
Copy link
Member

rhenium commented Nov 4, 2023

Sorry for the slow followup. In addition to #initialize, PKCS7.read_smime also needs this check.

It seems the enveloped-data content type has the same issue:

OpenSSL::PKCS7.new(OpenSSL::ASN1.Sequence([OpenSSL::ASN1.ObjectId("pkcs7-envelopedData")])).cipher="aes-128-cbc"

I didn't check other content types, but since there doesn't seem a valid use case for such encoding anyway (considering that CMS disallows it while claiming compatibility with PKCS#7), I think we can safely reject all PKCS7object->d.ptr == NULL.

@jeremyevans
Copy link
Contributor Author

@rhenium I updated PKCS7.read_smime to raise the same error.

@junaruga
Copy link
Member

junaruga commented Nov 12, 2023

This is not a new bug. I changed the base branch to the oldest supported one (gem 3.0/Ruby 3.1).

@rhenium Now, we see 227 commits on this PR to the maint-3.0 branch. So, is your intention only to merge the one original commit to the maint-3.0 branch manually later if the commit looks good to you? Do you plan to merge the commit to the master, maint-3.2, and maint-3.1 branches later?

I just wanted to share how I was backporting something as a reference. I was working for the master branch, the newest branch first, then backporting to the stable branches from the new branch to the old branch such as the maint-3.2, main-3.1, and main-3.0 branches. But it's just my preference. It doesn't mean I want you to change your way of backporting.

@pkuzco
Copy link
Contributor

pkuzco commented Mar 23, 2024

To completely fix the null-def issue the following change should be made on this PR:

diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c
index 09db06c..9167f57 100644
--- a/ext/openssl/ossl_pkcs7.c
+++ b/ext/openssl/ossl_pkcs7.c
@@ -159,24 +159,16 @@ ossl_pkcs7_s_read_smime(VALUE klass, VALUE arg)
     BIO *in, *out;
     PKCS7 *pkcs7;
     VALUE ret, data;
-    int i;
 
     ret = NewPKCS7(cPKCS7);
     in = ossl_obj2bio(&arg);
     out = NULL;
     pkcs7 = SMIME_read_PKCS7(in, &out);
     BIO_free(in);
-    if(!pkcs7) ossl_raise(ePKCS7Error, NULL);
-
-    i = OBJ_obj2nid(pkcs7->type);
-    switch(i){
-      case NID_pkcs7_signed:
-      case NID_pkcs7_signedAndEnveloped:
-        if (!pkcs7->d.sign)
-            ossl_raise(rb_eArgError, "No signed data in PKCS7");
-      default:
-        ; /* nothing */
-    }
+    if(!pkcs7)
+        ossl_raise(ePKCS7Error, "Could not parse the PKCS7");
+    if(!pkcs7->d.ptr)
+        ossl_raise(ePKCS7Error, "No content in PKCS7");
 
     data = out ? ossl_membio2str(out) : Qnil;
     SetPKCS7(ret, pkcs7);
@@ -345,7 +337,6 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self)
     PKCS7 *p7, *p7_orig = RTYPEDDATA_DATA(self);
     BIO *in;
     VALUE arg;
-    int i;
 
     if(rb_scan_args(argc, argv, "01", &arg) == 0)
        return self;
@@ -358,17 +349,9 @@ ossl_pkcs7_initialize(int argc, VALUE *argv, VALUE self)
     }
     BIO_free(in);
     if (!p7)
-        ossl_raise(rb_eArgError, "Could not parse the PKCS7");
-
-    i = OBJ_obj2nid(p7->type);
-    switch(i){
-      case NID_pkcs7_signed:
-      case NID_pkcs7_signedAndEnveloped:
-        if (!p7->d.sign)
-            ossl_raise(rb_eArgError, "No signed data in PKCS7");
-      default:
-        ; /* nothing */
-    }
+        ossl_raise(ePKCS7Error, "Could not parse the PKCS7");
+    if(!p7->d.ptr)
+        ossl_raise(ePKCS7Error, "No content in PKCS7");
 
     RTYPEDDATA_DATA(self) = p7;
     PKCS7_free(p7_orig);

@rhenium
Copy link
Member

rhenium commented Apr 30, 2024

Please see #752. I rebased this branch on top of maint-3.0 and applied @pkuzco's diff.

@rhenium rhenium closed this Apr 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet