From c7ae916fb5bcfe14c96976fcc7d1d5898a2700ca Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 4 Jan 2018 18:26:41 +0900 Subject: [PATCH 01/17] pkcs7: allow recipient's certificate to be omitted for PKCS7#decrypt The recipient's certificate is not mandatory for PKCS7_decrypt(). Make it possible to call OpenSSL::PKCS7#decrypt with only the private key to match the functionality. Reference: https://github.com/ruby/openssl/issues/182 --- ext/openssl/ossl_pkcs7.c | 4 ++-- test/test_pkcs7.rb | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 79ba0bdf4..28010c81f 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -803,9 +803,9 @@ ossl_pkcs7_decrypt(int argc, VALUE *argv, VALUE self) BIO *out; VALUE str; - rb_scan_args(argc, argv, "21", &pkey, &cert, &flags); + rb_scan_args(argc, argv, "12", &pkey, &cert, &flags); key = GetPrivPKeyPtr(pkey); /* NO NEED TO DUP */ - x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + x509 = NIL_P(cert) ? NULL : GetX509CertPtr(cert); /* NO NEED TO DUP */ flg = NIL_P(flags) ? 0 : NUM2INT(flags); GetPKCS7(self, p7); if(!(out = BIO_new(BIO_s_mem()))) diff --git a/test/test_pkcs7.rb b/test/test_pkcs7.rb index 149d3b9b5..6437112b7 100644 --- a/test/test_pkcs7.rb +++ b/test/test_pkcs7.rb @@ -133,6 +133,8 @@ def test_enveloped assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) assert_equal(3, recip[1].serial) assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) + + assert_equal(data, p7.decrypt(@rsa1024)) end def test_graceful_parsing_failure #[ruby-core:43250] From 23f96509e8cc6ac8de9f048de344c0f0a37dd808 Mon Sep 17 00:00:00 2001 From: Brian Cunnie Date: Mon, 29 Jan 2018 20:08:49 -0800 Subject: [PATCH 02/17] Correctly verify abbreviated IPv6 SANs IPv6 SAN-verification accommodates ["zero-compression"](https://tools.ietf.org/html/rfc5952#section-2.2). It also accommodates non-compressed addresses. Previously the verification of IPv6 addresses would fail unless the address syntax matched a specific format (no zero-compression, no leading zeroes). As an example, the IPv6 loopback address, if represented as `::1`, would not verify. Nor would it verify if represented as `0000:0000:0000:0000:0000:0000:0000:0001`; however, both representations are valid, RFC-compliant representations. The library would only accept a very specific representation (i.e. `0:0:0:0:0:0:0:1`). This commit addresses that shortcoming, and ensures that any valid IPv6 representation will correctly verify. --- lib/openssl/ssl.rb | 11 ++++++----- openssl.gemspec | 1 + test/test_ssl.rb | 6 +++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index 6a6f2b943..355eb2ebb 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -12,6 +12,7 @@ require "openssl/buffering" require "io/nonblock" +require "ipaddr" module OpenSSL module SSL @@ -272,11 +273,11 @@ def verify_certificate_identity(cert, hostname) return true if verify_hostname(hostname, san.value) when 7 # iPAddress in GeneralName (RFC5280) should_verify_common_name = false - # follows GENERAL_NAME_print() in x509v3/v3_alt.c - if san.value.size == 4 - return true if san.value.unpack('C*').join('.') == hostname - elsif san.value.size == 16 - return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname + if san.value.size == 4 || san.value.size == 16 + begin + return true if san.value == IPAddr.new(hostname).hton + rescue IPAddr::InvalidAddressError + end end end } diff --git a/openssl.gemspec b/openssl.gemspec index f721f247a..7c17cd549 100644 --- a/openssl.gemspec +++ b/openssl.gemspec @@ -17,6 +17,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 2.3.0" + spec.add_runtime_dependency "ipaddr" spec.add_development_dependency "rake" spec.add_development_dependency "rake-compiler" spec.add_development_dependency "test-unit", "~> 3.0" diff --git a/test/test_ssl.rb b/test/test_ssl.rb index 060c1f1cf..b8016677d 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -526,8 +526,12 @@ def test_verify_certificate_identity assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, "www.example.com\0.evil.com")) assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.255')) assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '192.168.7.1')) - assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) + assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13::17')) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '13::18')) assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '13:0:0:0:0:0:0:17')) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '44:0:0:0:0:0:0:17')) + assert_equal(true, OpenSSL::SSL.verify_certificate_identity(cert, '0013:0000:0000:0000:0000:0000:0000:0017')) + assert_equal(false, OpenSSL::SSL.verify_certificate_identity(cert, '1313:0000:0000:0000:0000:0000:0000:0017')) end end From c94599a16aa7e57affeeeac5139a48fd463d3ef2 Mon Sep 17 00:00:00 2001 From: ahadc Date: Sat, 24 Mar 2018 19:02:45 -0700 Subject: [PATCH 03/17] Update CONTRIBUTING.md Created a list format for the items required for a bug report. Also fixed a minor grammatical issue in the paragraph below the list. --- CONTRIBUTING.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e6e2a544..89f7e1d25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,12 +12,14 @@ If you think you found a bug, file a ticket on GitHub. Please DO NOT report security issues here, there is a separate procedure which is described on ["Security at ruby-lang.org"](https://www.ruby-lang.org/en/security/). -When reporting a bug, please make sure you include the version of Ruby, the -version of openssl gem, the version of the OpenSSL library, along with a sample -file that illustrates the problem or link to repository or gem that is -associated with the bug. - -There is a number of unresolved issues and feature requests for openssl that +When reporting a bug, please make sure you include: +* Ruby version +* OpenSSL gem version +* OpenSSL library version +* A sample file that illustrates the problem or link to the repository or + gem that is associated with the bug. + +There are a number of unresolved issues and feature requests for openssl that need review. Before submitting a new ticket, it is recommended to check [known issues] and [bugs.ruby-lang.org], the previous issue tracker. From fda02bb64193cdb8767265e46daf2e6cd1a2c6b5 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 13 Jul 2018 14:30:09 -0700 Subject: [PATCH 04/17] Move rb_global_variable call to directly after assignment While I'm guessing the INT2NUM calls all generate Fixnums and not Bignums, it's unwise to rely on that. Calling rb_global_variable directly after assignment is the pattern used in ossl.c, and it's probably best to do that here as well. --- ext/openssl/ossl_asn1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 7b6c97380..ab45bd833 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -1824,6 +1824,7 @@ do{\ rb_define_method(cASN1EndOfContent, "to_der", ossl_asn1eoc_to_der, 0); class_tag_map = rb_hash_new(); + rb_global_variable(&class_tag_map); rb_hash_aset(class_tag_map, cASN1EndOfContent, INT2NUM(V_ASN1_EOC)); rb_hash_aset(class_tag_map, cASN1Boolean, INT2NUM(V_ASN1_BOOLEAN)); rb_hash_aset(class_tag_map, cASN1Integer, INT2NUM(V_ASN1_INTEGER)); @@ -1847,7 +1848,6 @@ do{\ rb_hash_aset(class_tag_map, cASN1GeneralString, INT2NUM(V_ASN1_GENERALSTRING)); rb_hash_aset(class_tag_map, cASN1UniversalString, INT2NUM(V_ASN1_UNIVERSALSTRING)); rb_hash_aset(class_tag_map, cASN1BMPString, INT2NUM(V_ASN1_BMPSTRING)); - rb_global_variable(&class_tag_map); id_each = rb_intern_const("each"); } From 217b378cfaedb844fc2b436bfc14c3402f815768 Mon Sep 17 00:00:00 2001 From: nobu Date: Thu, 25 Jan 2018 11:21:47 +0000 Subject: [PATCH 05/17] openssl/buffering.rb: no RS when output * ext/openssl/lib/openssl/buffering.rb (do_write, puts): output methods should not be affected by the input record separator. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@62038 b2dd03c8-39d4-4d8f-98ff-823fe69b080e Sync-with-trunk: r62038 --- lib/openssl/buffering.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 935f61f0e..1f2b2a7e4 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -316,8 +316,8 @@ def do_write(s) @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 + if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex("\n") + remain = idx ? idx + 1 : @wbuffer.size nwritten = 0 while remain > 0 str = @wbuffer[nwritten,remain] @@ -409,9 +409,7 @@ def puts(*args) end args.each{|arg| s << arg.to_s - if $/ && /\n\z/ !~ s - s << "\n" - end + s.sub!(/(? Date: Sat, 4 Aug 2018 09:50:42 +0200 Subject: [PATCH 06/17] Reduce memory allocation when writing to SSLSocket At the moment OpenSSL::Buffering#do_write allocates some additional strings, and in my profiling writing 5MB of data allocates additional 7.7MB of strings. This patch greatly reduces memory allocations, and now writing 5MB of data allocates only additional 0.2MB of strings. This means that large file uploads would effectively not allocate additional memory anymore. Reference: https://bugs.ruby-lang.org/issues/14426 Reference: https://github.com/ruby/ruby/pull/1924 --- lib/openssl/buffering.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 1f2b2a7e4..5d1586e59 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -316,20 +316,15 @@ def do_write(s) @wbuffer << s @wbuffer.force_encoding(Encoding::BINARY) @sync ||= false - if @sync or @wbuffer.size > BLOCK_SIZE or idx = @wbuffer.rindex("\n") - remain = idx ? idx + 1 : @wbuffer.size - nwritten = 0 - while remain > 0 - str = @wbuffer[nwritten,remain] + if @sync or @wbuffer.size > BLOCK_SIZE + until @wbuffer.empty? begin - nwrote = syswrite(str) + nwrote = syswrite(@wbuffer) rescue Errno::EAGAIN retry end - remain -= nwrote - nwritten += nwrote + @wbuffer[0, nwrote] = "" end - @wbuffer[0,nwritten] = "" end end From 9890617b41be3fc493182997ab96c3b322725ac9 Mon Sep 17 00:00:00 2001 From: nobu Date: Fri, 21 Sep 2018 10:19:10 +0000 Subject: [PATCH 07/17] Remove -Wno-parentheses flag. [Fix GH-1958] From: Jun Aruga git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64806 b2dd03c8-39d4-4d8f-98ff-823fe69b080e * expand tabs. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64807 b2dd03c8-39d4-4d8f-98ff-823fe69b080e Suppress more -Wparentheses warnings [Fix GH-1958] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64808 b2dd03c8-39d4-4d8f-98ff-823fe69b080e [ky: this is a combined patch of r64806-r64808.] Sync-with-trunk: r64808 --- ext/openssl/openssl_missing.h | 2 +- ext/openssl/ossl_pkey.h | 10 +++++----- ext/openssl/ossl_pkey_dh.c | 2 +- ext/openssl/ossl_ssl.c | 4 +++- ext/openssl/ossl_x509name.c | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index 69a7df71d..09998214e 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -185,7 +185,7 @@ IMPL_KEY_ACCESSOR3(DSA, pqg, p, q, g, (p == obj->p || q == obj->q || g == obj->g #if !defined(OPENSSL_NO_DH) IMPL_PKEY_GETTER(DH, dh) IMPL_KEY_ACCESSOR2(DH, key, pub_key, priv_key, (pub_key == obj->pub_key || (obj->priv_key && priv_key == obj->priv_key))) -IMPL_KEY_ACCESSOR3(DH, pqg, p, q, g, (p == obj->p || obj->q && q == obj->q || g == obj->g)) +IMPL_KEY_ACCESSOR3(DH, pqg, p, q, g, (p == obj->p || (obj->q && q == obj->q) || g == obj->g)) static inline ENGINE *DH_get0_engine(DH *dh) { return dh->engine; } #endif diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index a2a9fc0df..0db59305f 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -133,9 +133,9 @@ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2, VALU BIGNUM *bn3 = NULL, *orig_bn3 = NIL_P(v3) ? NULL : GetBNPtr(v3);\ \ Get##_type(self, obj); \ - if (orig_bn1 && !(bn1 = BN_dup(orig_bn1)) || \ - orig_bn2 && !(bn2 = BN_dup(orig_bn2)) || \ - orig_bn3 && !(bn3 = BN_dup(orig_bn3))) { \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2))) || \ + (orig_bn3 && !(bn3 = BN_dup(orig_bn3)))) { \ BN_clear_free(bn1); \ BN_clear_free(bn2); \ BN_clear_free(bn3); \ @@ -163,8 +163,8 @@ static VALUE ossl_##_keytype##_set_##_group(VALUE self, VALUE v1, VALUE v2) \ BIGNUM *bn2 = NULL, *orig_bn2 = NIL_P(v2) ? NULL : GetBNPtr(v2);\ \ Get##_type(self, obj); \ - if (orig_bn1 && !(bn1 = BN_dup(orig_bn1)) || \ - orig_bn2 && !(bn2 = BN_dup(orig_bn2))) { \ + if ((orig_bn1 && !(bn1 = BN_dup(orig_bn1))) || \ + (orig_bn2 && !(bn2 = BN_dup(orig_bn2)))) { \ BN_clear_free(bn1); \ BN_clear_free(bn2); \ ossl_raise(eBNError, NULL); \ diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c index 31f3b8e72..bf4e3f932 100644 --- a/ext/openssl/ossl_pkey_dh.c +++ b/ext/openssl/ossl_pkey_dh.c @@ -262,7 +262,7 @@ ossl_dh_initialize_copy(VALUE self, VALUE other) BIGNUM *pub2 = BN_dup(pub); BIGNUM *priv2 = BN_dup(priv); - if (!pub2 || priv && !priv2) { + if (!pub2 || (priv && !priv2)) { BN_clear_free(pub2); BN_clear_free(priv2); ossl_raise(eDHError, "BN_dup"); diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index a85be17f0..7996f227b 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -184,8 +184,10 @@ ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v) for (i = 0; i < numberof(options_map); i++) { sum |= options_map[i].opts; - if (min && min > options_map[i].ver || max && max < options_map[i].ver) + if ((min && min > options_map[i].ver) || + (max && max < options_map[i].ver)) { opts |= options_map[i].opts; + } } SSL_CTX_clear_options(ctx, sum); SSL_CTX_set_options(ctx, opts); diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 0053f2e37..1ea8400db 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -270,7 +270,7 @@ x509name_print(VALUE self, unsigned long iflag) if (!out) ossl_raise(eX509NameError, NULL); ret = X509_NAME_print_ex(out, name, 0, iflag); - if (ret < 0 || iflag == XN_FLAG_COMPAT && ret == 0) { + if (ret < 0 || (iflag == XN_FLAG_COMPAT && ret == 0)) { BIO_free(out); ossl_raise(eX509NameError, "X509_NAME_print_ex"); } From 0c3ec4d423ef6622fb0f85ac119041cdd396ec40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janko=20Marohni=C4=87?= Date: Sat, 10 Nov 2018 11:59:31 +0100 Subject: [PATCH 08/17] Fix typo in docs --- ext/openssl/ossl_cipher.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 0840c84a7..aa336f35a 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -896,7 +896,7 @@ Init_ossl_cipher(void) * without processing the password further. A simple and secure way to * create a key for a particular Cipher is * - * cipher = OpenSSL::AES256.new(:CFB) + * cipher = OpenSSL::Cipher::AES256.new(:CFB) * cipher.encrypt * key = cipher.random_key # also sets the generated key on the Cipher * From 963507d82f904bb084f225871aa000f354c0dd18 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 14 Dec 2018 21:15:05 -0500 Subject: [PATCH 09/17] initial work creating the most basic CMS_ContentInfo object CMS_SignedInfo structure can not be created or freed, as it is just a pointer into CMS_ContentInfo stack. --- ext/openssl/ossl.c | 1 + ext/openssl/ossl.h | 2 + ext/openssl/ossl_cms.c | 498 +++++++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_cms.h | 21 ++ test/test_cms.rb | 263 ++++++++++++++++++++++ 5 files changed, 785 insertions(+) create mode 100644 ext/openssl/ossl_cms.c create mode 100644 ext/openssl/ossl_cms.h create mode 100644 test/test_cms.rb diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 38e650e1a..6eb25166d 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1202,6 +1202,7 @@ Init_openssl(void) Init_ossl_ns_spki(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); + Init_ossl_cms(); Init_ossl_pkey(); Init_ossl_rand(); Init_ossl_ssl(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 5a15839cb..18f1dbda3 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -165,6 +166,7 @@ void ossl_debug(const char *, ...); #include "ossl_ocsp.h" #include "ossl_pkcs12.h" #include "ossl_pkcs7.h" +#include "ossl_cms.h" #include "ossl_pkey.h" #include "ossl_rand.h" #include "ossl_ssl.h" diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c new file mode 100644 index 000000000..9ec65b8f7 --- /dev/null +++ b/ext/openssl/ossl_cms.c @@ -0,0 +1,498 @@ +/* + * 'OpenSSL for Ruby' project + * Copyright (C) 2018 Michael Richardson + * copied from ossl_pkcs7.c: + * Copyright (C) 2001-2002 Michal Rokos + * All rights reserved. + */ +/* + * This program is licensed under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#include "ossl.h" + +/* + * The CMS_ContentInfo is the primary data structure which this module creates and maintains + * Is is called OpenSSL::CMS::ContentInfo in ruby. + * + */ + +#define NewCMSContentInfo(klass) \ + TypedData_Wrap_Struct((klass), &ossl_cms_content_info_type, 0) +#define SetCMSContentInfo(obj, cmsci) do { \ + if (!(cmsci)) { \ + ossl_raise(rb_eRuntimeError, "CMS wasn't initialized."); \ + } \ + RTYPEDDATA_DATA(obj) = (cmsci); \ +} while (0) +#define GetCMSContentInfo(obj, cmsci) do { \ + TypedData_Get_Struct((obj), CMS_ContentInfo, &ossl_cms_content_info_type, (cmsci)); \ + if (!(cmsci)) { \ + ossl_raise(rb_eRuntimeError, "CMS wasn't initialized."); \ + } \ +} while (0) + +#define NewCMSsi(klass) \ + TypedData_Wrap_Struct((klass), &ossl_cms_signer_info_type, 0) +#define SetCMSsi(obj, cmssi) do { \ + if (!(cmssi)) { \ + ossl_raise(rb_eRuntimeError, "CMSsi wasn't initialized."); \ + } \ + RTYPEDDATA_DATA(obj) = (cmssi); \ +} while (0) +#define GetCMSsi(obj, cmssi) do { \ + TypedData_Get_Struct((obj), CMS_SignerInfo, &ossl_cms_signer_info_type, (cmssi)); \ + if (!(cmssi)) { \ + ossl_raise(rb_eRuntimeError, "CMSsi wasn't initialized."); \ + } \ +} while (0) + + +#define ossl_cmsci_set_data(o,v) rb_iv_set((o), "@data", (v)) +#define ossl_cmsci_get_data(o) rb_iv_get((o), "@data") +#define ossl_cmsci_set_err_string(o,v) rb_iv_set((o), "@error_string", (v)) +#define ossl_cmsci_get_err_string(o) rb_iv_get((o), "@error_string") + +VALUE cCMS; +VALUE cCMSContentInfo; +VALUE cCMSSignerInfo; +VALUE cCMSRecipient; +VALUE eCMSError; + + +static void +ossl_cms_content_info_free(void *ptr) +{ + CMS_ContentInfo_free(ptr); +} + +static const rb_data_type_t ossl_cms_content_info_type = { + "OpenSSL/CMS/ContentInfo", + { + 0, ossl_cms_content_info_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + +static void +ossl_cms_signer_info_free(void *ptr) +{ + /* nothing, only internal pointers are ever returned */ +} + +static const rb_data_type_t ossl_cms_signer_info_type = { + "OpenSSL/CMS/SignerInfo", + { + 0, ossl_cms_signer_info_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY, +}; + + + +static VALUE +ossl_cmsci_to_pem(VALUE self) +{ + CMS_ContentInfo *cmsci; + BIO *out; + VALUE str; + + GetCMSContentInfo(self, cmsci); + if (!(out = BIO_new(BIO_s_mem()))) { + ossl_raise(eCMSError, NULL); + } + if (!PEM_write_bio_CMS(out, cmsci)) { + BIO_free(out); + ossl_raise(ePKCS7Error, NULL); + } + str = ossl_membio2str(out); + + return str; +} + +/* + * call-seq: + * cmsci.to_der => binary + */ +static VALUE +ossl_cmsci_to_der(VALUE self) +{ + CMS_ContentInfo *cmsci; + VALUE str; + long len; + unsigned char *p; + + GetCMSContentInfo(self, cmsci); + if((len = i2d_CMS_ContentInfo(cmsci, NULL)) <= 0) + ossl_raise(eCMSError, NULL); + str = rb_str_new(0, len); + p = (unsigned char *)RSTRING_PTR(str); + if(i2d_CMS_ContentInfo(cmsci, &p) <= 0) + ossl_raise(eCMSError, NULL); + ossl_str_adjust(str, p); + + return str; +} + + +static VALUE +ossl_cmsci_alloc(VALUE klass) +{ + CMS_ContentInfo *cms; + VALUE obj; + + obj = NewCMSContentInfo(klass); + if (!(cms = CMS_ContentInfo_new())) { + ossl_raise(eCMSError, NULL); + } + SetCMSContentInfo(obj, cms); + + return obj; +} + +/* + * call-seq: + * CMS::ContentInfo.new => cmsci + * CMS::ContentInfo.new(string) => cmsi + * + * Create a new ContentInfo object. With argument decode from PEM or DER + * format CMS object. + * + */ +static VALUE +ossl_cmsci_initialize(int argc, VALUE *argv, VALUE self) +{ + CMS_ContentInfo *c1, *cms = DATA_PTR(self); + BIO *in; + VALUE arg; + + //GetCMSContentInfo(self, cms); + if(rb_scan_args(argc, argv, "01", &arg) == 0) + return self; + arg = ossl_to_der_if_possible(arg); + in = ossl_obj2bio(&arg); + c1 = PEM_read_bio_CMS(in, &cms, NULL, NULL); + if (!c1) { + OSSL_BIO_reset(in); + c1 = d2i_CMS_bio(in, &cms); + if (!c1) { + BIO_free(in); + CMS_ContentInfo_free(cms); + DATA_PTR(self) = NULL; + ossl_raise(rb_eArgError, "Could not parse the CMS"); + } + } + SetCMSContentInfo(self, cms); + BIO_free(in); + ossl_cmsci_set_data(self, Qnil); + ossl_cmsci_set_err_string(self, Qnil); + + return self; +} + +static VALUE +ossl_cmsci_verify(int argc, VALUE *argv, VALUE self) +{ + VALUE certs, store, indata, flags; + STACK_OF(X509) *x509s; + X509_STORE *x509st; + int flg, ok, status = 0; + BIO *in, *out; + CMS_ContentInfo *cmsci; + VALUE data; + const char *msg; + + GetCMSContentInfo(self, cmsci); + rb_scan_args(argc, argv, "22", &certs, &store, &indata, &flags); + x509st = GetX509StorePtr(store); + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + if(NIL_P(indata)) indata = ossl_cmsci_get_data(self); + in = NIL_P(indata) ? NULL : ossl_obj2bio(&indata); + if(NIL_P(certs)) x509s = NULL; + else{ + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } + } + if(!(out = BIO_new(BIO_s_mem()))){ + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(eCMSError, NULL); + } + ok = CMS_verify(cmsci, x509s, x509st, in, out, flg); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + if (ok < 0) ossl_raise(eCMSError, "CMS_verify"); + msg = ERR_reason_error_string(ERR_peek_error()); + ossl_cmsci_set_err_string(self, msg ? rb_str_new2(msg) : Qnil); + ossl_clear_error(); + data = ossl_membio2str(out); + ossl_cmsci_set_data(self, data); + + return (ok == 1) ? Qtrue : Qfalse; +} + + +static STACK_OF(X509) * +cmsci_get_certs(VALUE self) +{ + CMS_ContentInfo *cms; + STACK_OF(X509) *certs; + + GetCMSContentInfo(self, cms); + certs = CMS_get1_certs(cms); + return certs; +} + +static VALUE +ossl_cmsci_add_certificate(VALUE self, VALUE cert) +{ + CMS_ContentInfo *cms; + X509 *x509; + + GetCMSContentInfo(self, cms); + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + if (!CMS_add1_cert(cms, x509)){ /* add1() takes reference */ + ossl_raise(eCMSError, NULL); + } + + return self; +} + +static VALUE +ossl_cmsci_set_certs_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, arg)) +{ + return ossl_cmsci_add_certificate(arg, i); +} + +static VALUE +ossl_cmsci_set_certificates(VALUE self, VALUE ary) +{ + STACK_OF(X509) *certs; + X509 *cert; + + certs = cmsci_get_certs(self); + while((cert = sk_X509_pop(certs))) X509_free(cert); + rb_block_call(ary, rb_intern("each"), 0, 0, ossl_cmsci_set_certs_i, self); + + return ary; +} + +static VALUE +ossl_cmsci_get_certificates(VALUE self) +{ + return ossl_x509_sk2ary(cmsci_get_certs(self)); +} + +/* + * CMS SignerInfo is not a first class object, but part of the + * CMS ContentInfo. It can be wrapped in a ruby object, but it can + * not be created or freed directly. + */ +static VALUE +ossl_cmssi_new(CMS_SignerInfo *cmssi) +{ + VALUE obj; + + obj = NewCMSsi(cCMSSignerInfo); + SetCMSsi(obj, cmssi); + + return obj; +} + +static VALUE +ossl_cmssi_get_issuer(VALUE self) +{ + CMS_SignerInfo *cmssi; + ASN1_OCTET_STRING *keyid; + X509_NAME *issuer; + ASN1_INTEGER *sno; + + GetCMSsi(self, cmssi); + + if(CMS_SignerInfo_get0_signer_id(cmssi,&keyid,&issuer, &sno)!=1) { + ossl_raise(eCMSError, "get0_signer_id failed"); + } + + /* XXX keyid may be set instead */ + if(issuer) { + return ossl_x509name_new(issuer); + } else { + return Qnil; + } +} + +static VALUE +ossl_cmssi_get_serial(VALUE self) +{ + CMS_SignerInfo *cmssi; + ASN1_OCTET_STRING *keyid; + X509_NAME *issuer; + ASN1_INTEGER *sno; + + GetCMSsi(self, cmssi); + + if(CMS_SignerInfo_get0_signer_id(cmssi,&keyid,&issuer, &sno)!=1) { + ossl_raise(eCMSError, "get0_signer_id failed"); + } + + /* XXX keyid may be set */ + if(sno) { + return asn1integer_to_num(sno); + } else { + return Qnil; + } +} + +static VALUE +ossl_cmsci_get_signers(VALUE self) +{ + CMS_ContentInfo *cms; + STACK_OF(CMS_SignerInfo) *sk; + CMS_SignerInfo *si; + int num, i; + VALUE ary; + + GetCMSContentInfo(self, cms); + if (!(sk = CMS_get0_SignerInfos(cms))) { + OSSL_Debug("OpenSSL::CMS#get_signer_info == NULL!"); + return rb_ary_new(); + } + if ((num = sk_CMS_SignerInfo_num(sk)) < 0) { + ossl_raise(eCMSError, "Negative number of signers!"); + } + ary = rb_ary_new2(num); + for (i=0; i cms + * + * CMS.sign creates and returns a CMS SignedData structure. + * The data will be signed with *key* (An OpenSSL::PKey instance), and the list of + * certs (if any) will be included in the structure as additional + * anchors. + * + * The flags come from the set of XYZ. + * + */ +static VALUE +ossl_cms_s_sign(int argc, VALUE *argv, VALUE klass) +{ + VALUE cert, key, data, certs, flags; + X509 *x509; + EVP_PKEY *pkey; + BIO *in; + STACK_OF(X509) *x509s; + int flg, status = 0; + CMS_ContentInfo *cms_cinfo; + VALUE ret; + + rb_scan_args(argc, argv, "32", &cert, &key, &data, &certs, &flags); + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + ret = NewCMSContentInfo(cCMSContentInfo); + in = ossl_obj2bio(&data); + if(NIL_P(certs)) x509s = NULL; + else{ + x509s = ossl_protect_x509_ary2sk(certs, &status); + if(status){ + BIO_free(in); + rb_jump_tag(status); + } + } + if(!(cms_cinfo = CMS_sign(x509, pkey, x509s, in, flg))){ + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + ossl_raise(ePKCS7Error, NULL); + } + SetCMSContentInfo(ret, cms_cinfo); + ossl_cmsci_set_data(ret, data); + ossl_cmsci_set_err_string(ret, Qnil); + BIO_free(in); + sk_X509_pop_free(x509s, X509_free); + + return ret; +} + +/* + * INIT + */ +void +Init_ossl_cms(void) +{ + cCMS = rb_define_class_under(mOSSL, "CMS", rb_cObject); + rb_define_singleton_method(cCMS, "sign", ossl_cms_s_sign, -1); + + eCMSError = rb_define_class_under(cCMS, "CMSError", eOSSLError); + + cCMSContentInfo = rb_define_class_under(cCMS, "ContentInfo", rb_cObject); + rb_define_alloc_func(cCMSContentInfo, ossl_cmsci_alloc); + rb_define_method(cCMSContentInfo, "to_pem", ossl_cmsci_to_pem, 0); + rb_define_alias(cCMSContentInfo, "to_s", "to_pem"); + rb_define_method(cCMSContentInfo, "to_der", ossl_cmsci_to_der, 0); + rb_define_method(cCMSContentInfo, "initialize", ossl_cmsci_initialize, -1); + + rb_define_method(cCMSContentInfo, "certificates=", ossl_cmsci_set_certificates, 1); + rb_define_method(cCMSContentInfo, "certificates", ossl_cmsci_get_certificates, 0); + rb_define_method(cCMSContentInfo, "signers", ossl_cmsci_get_signers, 0); + rb_define_method(cCMSContentInfo, "verify", ossl_cmsci_verify, -1); + rb_attr(cCMSContentInfo, rb_intern("data"), 1, 0, Qfalse); + rb_attr(cCMSContentInfo, rb_intern("error_string"), 1, 1, Qfalse); +#if 0 + rb_define_method(cCMSContentInfo, "add_signer", ossl_cmsci_add_signer, 1); +#endif + + cCMSSignerInfo = rb_define_class_under(cCMS, "SignerInfo", rb_cObject); + rb_define_method(cCMSSignerInfo,"issuer", ossl_cmssi_get_issuer, 0); + rb_define_alias(cCMSSignerInfo, "name", "issuer"); + rb_define_method(cCMSSignerInfo,"serial", ossl_cmssi_get_serial,0); + +#if 0 + rb_define_singleton_method(cCMS, "read_smime", ossl_cms_s_read_smime, 1); + rb_define_singleton_method(cCMS, "write_smime", ossl_cms_s_write_smime, -1); + rb_define_singleton_method(cCMS, "encrypt", ossl_cms_s_encrypt, -1); + rb_define_method(cCMS, "initialize_copy", ossl_cms_copy, 1); + rb_define_method(cCMS, "detached=", ossl_cms_set_detached, 1); + rb_define_method(cCMS, "detached", ossl_cms_get_detached, 0); + rb_define_method(cCMS, "detached?", ossl_cms_detached_p, 0); + rb_define_method(cCMS, "cipher=", ossl_cms_set_cipher, 1); + rb_define_method(cCMS, "add_recipient", ossl_cms_add_recipient, 1); + rb_define_method(cCMS, "recipients", ossl_cms_get_recipient, 0); + rb_define_method(cCMS, "add_certificate", ossl_cms_add_certificate, 1); + rb_define_method(cCMS, "add_crl", ossl_cms_add_crl, 1); + rb_define_method(cCMS, "crls=", ossl_cms_set_crls, 1); + rb_define_method(cCMS, "crls", ossl_cms_get_crls, 0); + rb_define_method(cCMS, "add_data", ossl_cms_add_data, 1); + rb_define_alias(cCMS, "data=", "add_data"); + rb_define_method(cCMS, "decrypt", ossl_cms_decrypt, -1); + + cCMSRecipient = rb_define_class_under(cCMS,"RecipientInfo",rb_cObject); + rb_define_alloc_func(cCMSRecipient, ossl_cmsri_alloc); + rb_define_method(cCMSRecipient, "initialize", ossl_cmsri_initialize,1); + rb_define_method(cCMSRecipient, "issuer", ossl_cmsri_get_issuer,0); + rb_define_method(cCMSRecipient, "serial", ossl_cmsri_get_serial,0); + rb_define_method(cCMSRecipient, "enc_key", ossl_cmsri_get_enc_key,0); +#endif + +#define DefCMSConst(x) rb_define_const(cCMS, #x, INT2NUM(CMS_##x)) + + DefCMSConst(TEXT); + DefCMSConst(NOCERTS); + DefCMSConst(DETACHED); + DefCMSConst(BINARY); + DefCMSConst(NOATTR); + DefCMSConst(NOSMIMECAP); + DefCMSConst(USE_KEYID); + DefCMSConst(STREAM); + DefCMSConst(PARTIAL); +} diff --git a/ext/openssl/ossl_cms.h b/ext/openssl/ossl_cms.h new file mode 100644 index 000000000..e0f03f5d7 --- /dev/null +++ b/ext/openssl/ossl_cms.h @@ -0,0 +1,21 @@ +/* + * 'OpenSSL for Ruby' project + * Copyright (C) 2001-2002 Michal Rokos + * All rights reserved. + */ +/* + * This program is licensed under the same licence as Ruby. + * (See the file 'LICENCE'.) + */ +#if !defined(_OSSL_CMS_H_) +#define _OSSL_CMS_H_ + +extern VALUE cCMS; +extern VALUE cCMSContentInfo; +extern VALUE cCMSSigner; +extern VALUE cCMSRecipient; +extern VALUE eCMSError; + +void Init_ossl_cms(void); + +#endif /* _OSSL_CMS_H_ */ diff --git a/test/test_cms.rb b/test/test_cms.rb new file mode 100644 index 000000000..dad2e0063 --- /dev/null +++ b/test/test_cms.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: false +require_relative 'utils' + +if defined?(OpenSSL) + +class OpenSSL::TestCMS < OpenSSL::TestCase + def setup + super + @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::CMS.sign(@ee1_cert, @rsa1024, data, ca_certs) + byebug + cms = OpenSSL::CMS::ContentInfo.new(tmp.to_der) + certs = cms.certificates + signers = cms.signers + assert(cms.verify([], store)) + assert_equal(data, cms.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, CMS::BINARY flag should be used. + + data = "aaaaa\nbbbbb\nccccc\n" + flag = OpenSSL::CMS::BINARY + tmp = OpenSSL::CMS.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) + cms = OpenSSL::CMS::ContentInfo.new(tmp.to_der) + certs = cms.certificates + signers = cms.signers + assert(cms.verify([], store)) + assert_equal(data, cms.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::CMS.sign(@ee1_cert, @rsa1024, data, [], flag) + tmp2 = OpenSSL::CMS.sign(@ee2_cert, @rsa1024, data, [], flag) + tmp1.add_signer(tmp2.signers[0]) + tmp1.add_certificate(@ee2_cert) + + cms = OpenSSL::CMS.ContentInfo.new(tmp1.to_der) + certs = cms.certificates + signers = cms.signers + assert(cms.verify([], store)) + assert_equal(data, cms.data) + assert_equal(2, certs.size) + assert_equal(2, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + assert_equal(@ee2_cert.serial, signers[1].serial) + assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s) + end + + def test_detached_sign + pend "not yet" + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ca_certs = [@ca_cert] + + data = "aaaaa\nbbbbb\nccccc\n" + flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED + tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + assert_nothing_raised do + OpenSSL::ASN1.decode(p7) + end + + certs = p7.certificates + signers = p7.signers + assert(!p7.verify([], store)) + assert(p7.verify([], store, data)) + assert_equal(data, p7.data) + assert_equal(2, certs.size) + assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) + assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) + assert_equal(1, signers.size) + assert_equal(@ee1_cert.serial, signers[0].serial) + assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) + end + + def test_enveloped + pend "not yet" + certs = [@ee1_cert, @ee2_cert] + cipher = OpenSSL::Cipher::AES.new("128-CBC") + data = "aaaaa\nbbbbb\nccccc\n" + + tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY) + p7 = OpenSSL::PKCS7.new(tmp.to_der) + recip = p7.recipients + assert_equal(:enveloped, p7.type) + assert_equal(2, recip.size) + + assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s) + assert_equal(2, recip[0].serial) + assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert)) + + assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) + assert_equal(3, recip[1].serial) + assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) + + assert_equal(data, p7.decrypt(@rsa1024)) + end + + def test_graceful_parsing_failure #[ruby-core:43250] + pend "not yet" + contents = File.read(__FILE__) + assert_raise(ArgumentError) { OpenSSL::PKCS7.new(contents) } + end + + def test_degenerate_cms + pend "not yet" + ca_cert_pem = < Date: Mon, 24 Dec 2018 14:34:57 -0500 Subject: [PATCH 10/17] remove pending test cases, clean up debug statments --- test/test_cms.rb | 217 ++++++----------------------------------------- 1 file changed, 25 insertions(+), 192 deletions(-) diff --git a/test/test_cms.rb b/test/test_cms.rb index dad2e0063..7f2eb66f1 100644 --- a/test/test_cms.rb +++ b/test/test_cms.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require_relative 'utils' -if defined?(OpenSSL) +if defined?(OpenSSL::CMS) class OpenSSL::TestCMS < OpenSSL::TestCase def setup @@ -35,7 +35,6 @@ def test_signed data = "aaaaa\r\nbbbbb\r\nccccc\r\n" tmp = OpenSSL::CMS.sign(@ee1_cert, @rsa1024, data, ca_certs) - byebug cms = OpenSSL::CMS::ContentInfo.new(tmp.to_der) certs = cms.certificates signers = cms.signers @@ -67,197 +66,31 @@ def test_signed 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::CMS.sign(@ee1_cert, @rsa1024, data, [], flag) - tmp2 = OpenSSL::CMS.sign(@ee2_cert, @rsa1024, data, [], flag) - tmp1.add_signer(tmp2.signers[0]) - tmp1.add_certificate(@ee2_cert) - - cms = OpenSSL::CMS.ContentInfo.new(tmp1.to_der) - certs = cms.certificates - signers = cms.signers - assert(cms.verify([], store)) - assert_equal(data, cms.data) - assert_equal(2, certs.size) - assert_equal(2, signers.size) - assert_equal(@ee1_cert.serial, signers[0].serial) - assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) - assert_equal(@ee2_cert.serial, signers[1].serial) - assert_equal(@ee2_cert.issuer.to_s, signers[1].issuer.to_s) - end - - def test_detached_sign - pend "not yet" - store = OpenSSL::X509::Store.new - store.add_cert(@ca_cert) - ca_certs = [@ca_cert] - - data = "aaaaa\nbbbbb\nccccc\n" - flag = OpenSSL::PKCS7::BINARY|OpenSSL::PKCS7::DETACHED - tmp = OpenSSL::PKCS7.sign(@ee1_cert, @rsa1024, data, ca_certs, flag) - p7 = OpenSSL::PKCS7.new(tmp.to_der) - assert_nothing_raised do - OpenSSL::ASN1.decode(p7) - end - - certs = p7.certificates - signers = p7.signers - assert(!p7.verify([], store)) - assert(p7.verify([], store, data)) - assert_equal(data, p7.data) - assert_equal(2, certs.size) - assert_equal(@ee1_cert.subject.to_s, certs[0].subject.to_s) - assert_equal(@ca_cert.subject.to_s, certs[1].subject.to_s) - assert_equal(1, signers.size) - assert_equal(@ee1_cert.serial, signers[0].serial) - assert_equal(@ee1_cert.issuer.to_s, signers[0].issuer.to_s) - end - - def test_enveloped - pend "not yet" - certs = [@ee1_cert, @ee2_cert] - cipher = OpenSSL::Cipher::AES.new("128-CBC") - data = "aaaaa\nbbbbb\nccccc\n" - - tmp = OpenSSL::PKCS7.encrypt(certs, data, cipher, OpenSSL::PKCS7::BINARY) - p7 = OpenSSL::PKCS7.new(tmp.to_der) - recip = p7.recipients - assert_equal(:enveloped, p7.type) - assert_equal(2, recip.size) - - assert_equal(@ca_cert.subject.to_s, recip[0].issuer.to_s) - assert_equal(2, recip[0].serial) - assert_equal(data, p7.decrypt(@rsa1024, @ee1_cert)) - - assert_equal(@ca_cert.subject.to_s, recip[1].issuer.to_s) - assert_equal(3, recip[1].serial) - assert_equal(data, p7.decrypt(@rsa1024, @ee2_cert)) - - assert_equal(data, p7.decrypt(@rsa1024)) - end - - def test_graceful_parsing_failure #[ruby-core:43250] - pend "not yet" - contents = File.read(__FILE__) - assert_raise(ArgumentError) { OpenSSL::PKCS7.new(contents) } - end - - def test_degenerate_cms - pend "not yet" - ca_cert_pem = < Date: Thu, 27 Dec 2018 10:58:45 -0500 Subject: [PATCH 11/17] do not include CMS code with LIBRESSL, as it has no CMS code --- ext/openssl/extconf.rb | 1 + ext/openssl/ossl.c | 2 ++ ext/openssl/ossl.h | 2 ++ ext/openssl/ossl_cms.c | 3 +++ 4 files changed, 8 insertions(+) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 4f218562b..bc74487c6 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -165,6 +165,7 @@ def find_openssl_library have_func("X509_get0_notBefore") have_func("SSL_SESSION_get_protocol_version") have_func("EVP_PBE_scrypt") +have_func("CMS_sign") Logging::message "=== Checking done. ===\n" diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 6eb25166d..b5b121e00 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1202,7 +1202,9 @@ Init_openssl(void) Init_ossl_ns_spki(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); +#if defined(HAVE_CMS_SIGN) Init_ossl_cms(); +#endif Init_ossl_pkey(); Init_ossl_rand(); Init_ossl_ssl(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 18f1dbda3..81b2a8387 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -24,7 +24,9 @@ #include #include #include +#if defined(HAVE_CMS_SIGN) #include +#endif #include #include #include diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index 9ec65b8f7..2367451da 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -11,6 +11,7 @@ */ #include "ossl.h" +#if defined(HAVE_CMS_SIGN) /* * The CMS_ContentInfo is the primary data structure which this module creates and maintains * Is is called OpenSSL::CMS::ContentInfo in ruby. @@ -496,3 +497,5 @@ Init_ossl_cms(void) DefCMSConst(STREAM); DefCMSConst(PARTIAL); } + +#endif /* HAVE_CMS_SIGN */ From 00638824373f7b523f3978c0283b7be571654aa8 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Wed, 10 Jan 2018 10:51:22 -0500 Subject: [PATCH 12/17] bump version and point at correct version of openssl --- openssl.gemspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openssl.gemspec b/openssl.gemspec index 7c17cd549..90d496a73 100644 --- a/openssl.gemspec +++ b/openssl.gemspec @@ -1,11 +1,11 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "2.1.2" - spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] - spec.email = ["ruby-core@ruby-lang.org"] + spec.version = "2.2.0-mcr1" + spec.authors = ["Michael Richardson", "Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] + spec.email = ["mcr@sandelman.ca","ruby-core@ruby-lang.org"] spec.summary = %q{OpenSSL provides SSL, TLS and general purpose cryptography.} - spec.description = %q{It wraps the OpenSSL library.} - spec.homepage = "https://github.com/ruby/openssl" + spec.description = %q{It wraps the OpenSSL library. Note this version depends upon an as-yet-unreleased version of OpenSSL. Built it from https://github.com/mcr/openssl/tree/dtls-listen-refactor.} + spec.homepage = "https://github.com/mcr/ruby-openssl" spec.license = "Ruby" spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] From 690d437147f079643225009c8074fd895ebc9885 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Thu, 11 Jan 2018 12:30:13 -0500 Subject: [PATCH 13/17] added notes about building with DTLS support --- DTLS.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 DTLS.md diff --git a/DTLS.md b/DTLS.md new file mode 100644 index 000000000..0e9e2d7f7 --- /dev/null +++ b/DTLS.md @@ -0,0 +1,43 @@ + +In order to get DTLS to work, you need a patched copy of Openssl. +Get it here: + https://github.com/mcr/openssl/tree/dtls-listen-refactor + +build and install it. You might want to compile without DSO support, as that will +make it easier for the ruby-openssl module to link in the right code. To do +that you can do: + ./Configure no-shared --prefix=/sandel/3rd/openssl --debug linux-x86_64 + +(--debug being optional) + +The resulting openssl.so will be significantly bigger, btw: + %size tmp/x86_64-linux/openssl/2.4.1/openssl.so + text data bss dec hex filename + 3889567 261788 16856 4168211 3f9a13 tmp/x86_64-linux/openssl/2.4.1/openssl.so + + +Pick a --prefix which is not on your regular paths. Probably gem can be +persuaded to do all of this, but hopefully the code will upstreamed sooner +and the problem will go away. + +If DTLSv1_accept() is not available, then the DTLS support will not include +server side code, only client side code. No patches are necessary to make +client-side DTLS work. To be sure that the patch has been found is enabled +check for: + + checking for DTLSv1_accept()... yes + + +Then build with: + + rake compile -- --with-openssl-dir=/sandel/3rd/openssl + +I don't know how to add the extra arguments required to your Gemfile so that +it will be built properly during bundle processing. I'm sure that there is a way, +patches welcome. I do: + gem build openssl + gem install ./openssl-2.2.0.pre.mcr1.gem + +BTW: the pull request is at: + https://github.com/openssl/openssl/pull/5024 +and comments would be welcome. From 533adb219646681af7c1ededc6c23e0644d70508 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Sat, 26 Aug 2017 20:09:38 -0400 Subject: [PATCH 14/17] instead of looking of NIDs and then using X509V3_EXT_nconf_nid, instead just pass strings to X509V3_EXT_nconf, which has all the logic for processing dealing with generic extensions also process the oid through ln2nid() to retain compatibility. --- ext/openssl/ossl_x509ext.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/openssl/ossl_x509ext.c b/ext/openssl/ossl_x509ext.c index 30ec09d7a..9808a324f 100644 --- a/ext/openssl/ossl_x509ext.c +++ b/ext/openssl/ossl_x509ext.c @@ -209,15 +209,16 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) int nid; VALUE rconf; CONF *conf; + const char *oid_cstr = NULL; rb_scan_args(argc, argv, "21", &oid, &value, &critical); - StringValueCStr(oid); StringValue(value); if(NIL_P(critical)) critical = Qfalse; - nid = OBJ_ln2nid(RSTRING_PTR(oid)); - if(!nid) nid = OBJ_sn2nid(RSTRING_PTR(oid)); - if(!nid) ossl_raise(eX509ExtError, "unknown OID `%"PRIsVALUE"'", oid); + oid_cstr = StringValueCStr(oid); + nid = OBJ_ln2nid(oid_cstr); + if (nid != NID_undef) + oid_cstr = OBJ_nid2sn(nid); valstr = rb_str_new2(RTEST(critical) ? "critical," : ""); rb_str_append(valstr, value); @@ -228,7 +229,8 @@ ossl_x509extfactory_create_ext(int argc, VALUE *argv, VALUE self) rconf = rb_iv_get(self, "@config"); conf = NIL_P(rconf) ? NULL : DupConfigPtr(rconf); X509V3_set_nconf(ctx, conf); - ext = X509V3_EXT_nconf_nid(conf, ctx, nid, RSTRING_PTR(valstr)); + + ext = X509V3_EXT_nconf(conf, ctx, oid_cstr, RSTRING_PTR(valstr)); X509V3_set_ctx_nodb(ctx); NCONF_free(conf); if (!ext){ From 84b51cd3d99a1aca281f8b6d42d3598ec7a95491 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 25 Jan 2019 18:27:03 -0500 Subject: [PATCH 15/17] added NO_SIGNER_CERT_VERIFY and NOINTERN constants --- ext/openssl/ossl_cms.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index 2367451da..fe3006f3d 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -487,6 +487,8 @@ Init_ossl_cms(void) #define DefCMSConst(x) rb_define_const(cCMS, #x, INT2NUM(CMS_##x)) + DefCMSConst(NO_SIGNER_CERT_VERIFY); + DefCMSConst(NOINTERN); DefCMSConst(TEXT); DefCMSConst(NOCERTS); DefCMSConst(DETACHED); From 2862335003c321ac0998a82fb542120f9dd4b50c Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 25 Jan 2019 18:32:44 -0500 Subject: [PATCH 16/17] added read_derpub, to only attempt to load DER encoded public keys --- ext/openssl/ossl_pkey.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index e1fffb244..f4d31fc95 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -185,6 +185,40 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) return ossl_pkey_new(pkey); } +/* + * call-seq: + * OpenSSL::PKey.read_derpub(string [, pwd ]) -> PKey + * OpenSSL::PKey.read_derpub(io [, pwd ]) -> PKey + * + * Reads a DER encoded string from _string_ or _io_ and returns an + * instance of the a public key object. + * + * === Parameters + * * _string+ is a DER-encoded string containing an arbitrary public key. + * * _io_ is an instance of IO containing a DER-encoded + * arbitrary public key. + */ +static VALUE +ossl_pkey_new_pub_from_data(int argc, VALUE *argv, VALUE self) +{ + EVP_PKEY *pkey; + BIO *bio; + VALUE data; + + rb_scan_args(argc, argv, "1", &data); + + bio = ossl_obj2bio(&data); + if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) { + OSSL_BIO_reset(bio); + } + + BIO_free(bio); + if (!pkey) + ossl_raise(ePKeyError, "Could not parse PKey"); + + return ossl_pkey_new(pkey); +} + void ossl_pkey_check_public_key(const EVP_PKEY *pkey) { @@ -488,6 +522,7 @@ Init_ossl_pkey(void) cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject); rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1); + rb_define_module_function(mPKey, "read_derpub", ossl_pkey_new_pub_from_data, -1); rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); From f00f1ea7654041296b98a2729326dc8648861694 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 5 Feb 2019 10:57:20 +0100 Subject: [PATCH 17/17] updated comments about documentation strings --- ext/openssl/ossl_x509name.c | 5 +++++ ext/openssl/ossl_x509req.c | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 1ea8400db..e2cb18fc9 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -333,6 +333,11 @@ ossl_x509name_inspect(VALUE self) * * Returns an Array representation of the distinguished name suitable for * passing to ::new + * The type code is an integer represents the string type. Typical values include: + * 12 - UTF8STRING + * 19 - PRINTABLESTRING + * more values can be found, at, i.e: + * https://docs.huihoo.com/doxygen/openssl/1.0.1c/crypto_2asn1_2asn1_8h.html */ static VALUE ossl_x509name_to_a(VALUE self) diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index 2c20042a9..6a409f81f 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -318,7 +318,12 @@ ossl_x509req_sign(VALUE self, VALUE key, VALUE digest) } /* - * Checks that cert signature is made with PRIVversion of this PUBLIC 'key' + * call-seq: + * request.verify(pub_key) => true/false + * + * Validates the signature on the CSR. The public key is usually found + * in request.public_key. + * */ static VALUE ossl_x509req_verify(VALUE self, VALUE key)