From 51b28bcc5193e30ff26f36484edf6d172a4e5223 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 14 Dec 2018 21:15:05 -0500 Subject: [PATCH 1/6] This adds CMS support to the Ruby layer. The CMS_ContentInfo object can be used. Access to the CMS_SignedInfo structure exists, but it can not be created or freed, as it is just a pointer into CMS_ContentInfo stack. feat: added NOINTERN and NO_SIGNER_CERT_VERIFY flags feat: look for CMS_sign, and set HAVE_CMS_SIGN if present, enabling CMS support --- ext/openssl/extconf.rb | 3 + ext/openssl/ossl.c | 3 + ext/openssl/ossl.h | 3 + ext/openssl/ossl_cms.c | 514 +++++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_cms.h | 21 ++ ext/openssl/ossl_pkcs7.c | 2 +- ext/openssl/ossl_pkcs7.h | 1 + test/openssl/test_cms.rb | 99 ++++++++ 8 files changed, 645 insertions(+), 1 deletion(-) create mode 100644 ext/openssl/ossl_cms.c create mode 100644 ext/openssl/ossl_cms.h create mode 100644 test/openssl/test_cms.rb diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 8aac52ef4..f53915f02 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -143,6 +143,9 @@ def find_openssl_library # added in 1.1.0, currently not in LibreSSL have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0)", evp_h) +# look for CMS code, normally included, but some variations compile it out +have_func("CMS_sign", ssl_h) + # added in OpenSSL 1.1.1 and LibreSSL 3.5.0, then removed in LibreSSL 4.0.0 have_func("EVP_PKEY_check(NULL)", evp_h) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 60780790b..d0d885d3b 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1044,6 +1044,9 @@ Init_openssl(void) Init_ossl_ocsp(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); +#if defined(HAVE_CMS_SIGN) + Init_ossl_cms(); +#endif Init_ossl_pkey(); Init_ossl_provider(); Init_ossl_rand(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 22471d208..d57fe2de2 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include #ifndef OPENSSL_NO_TS @@ -193,6 +195,7 @@ extern VALUE dOSSL; #include "ossl_ocsp.h" #include "ossl_pkcs12.h" #include "ossl_pkcs7.h" +#include "ossl_cms.h" #include "ossl_pkey.h" #include "ossl_provider.h" #include "ossl_rand.h" diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c new file mode 100644 index 000000000..ac2199ac0 --- /dev/null +++ b/ext/openssl/ossl_cms.c @@ -0,0 +1,514 @@ +/* + * '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" + +#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. + * + */ + +#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; + + x509 = NULL; + pkey = NULL; + in = NULL; + rb_scan_args(argc, argv, "32", &cert, &key, &data, &certs, &flags); + if(!NIL_P(cert)) { + x509 = GetX509CertPtr(cert); /* NO NEED TO DUP */ + } + if(!NIL_P(key)) { + pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */ + } + flg = NIL_P(flags) ? 0 : NUM2INT(flags); + ret = NewCMSContentInfo(cCMSContentInfo); + if(!NIL_P(data)) { + 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_undef_alloc_func(cCMSSignerInfo); + 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(NO_SIGNER_CERT_VERIFY); + DefCMSConst(NOINTERN); + DefCMSConst(TEXT); + DefCMSConst(NOCERTS); + DefCMSConst(DETACHED); + DefCMSConst(BINARY); + DefCMSConst(NOATTR); + DefCMSConst(NOSMIMECAP); + DefCMSConst(USE_KEYID); + DefCMSConst(STREAM); + DefCMSConst(PARTIAL); +} + +#endif /* HAVE_CMS_SIGN */ 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/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index 4d719c96d..a863acfe4 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -67,7 +67,7 @@ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; -static VALUE ePKCS7Error; +VALUE ePKCS7Error; static void ossl_pkcs7_free(void *ptr) diff --git a/ext/openssl/ossl_pkcs7.h b/ext/openssl/ossl_pkcs7.h index 140fda183..4cf3e420b 100644 --- a/ext/openssl/ossl_pkcs7.h +++ b/ext/openssl/ossl_pkcs7.h @@ -10,6 +10,7 @@ #if !defined(_OSSL_PKCS7_H_) #define _OSSL_PKCS7_H_ +extern VALUE ePKCS7Error; VALUE ossl_pkcs7_new(PKCS7 *p7); void Init_ossl_pkcs7(void); diff --git a/test/openssl/test_cms.rb b/test/openssl/test_cms.rb new file mode 100644 index 000000000..2b6d3a30e --- /dev/null +++ b/test/openssl/test_cms.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: false +require_relative 'utils' + +if defined?(OpenSSL::CMS) + +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 + # cms.signers does not produce FIPS compliant things (not sure why) + omit_on_fips + + 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) + 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) + + if false + # multiple signers not yet supported. + # 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 + end + +end +end # if(OpenSSL) From 1fc43868940d472676b60f544b1f002ae14aa98d Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 28 Jul 2025 09:26:38 -0400 Subject: [PATCH 2/6] review comments on PR: always define Init_ossl_cms() --- ext/openssl/ossl.c | 4 +--- ext/openssl/ossl_cms.c | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index d0d885d3b..dae85fcac 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1035,6 +1035,7 @@ Init_openssl(void) Init_ossl_asn1(); Init_ossl_bn(); Init_ossl_cipher(); + Init_ossl_cms(); Init_ossl_config(); Init_ossl_digest(); Init_ossl_engine(); @@ -1044,9 +1045,6 @@ Init_openssl(void) Init_ossl_ocsp(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); -#if defined(HAVE_CMS_SIGN) - Init_ossl_cms(); -#endif Init_ossl_pkey(); Init_ossl_provider(); Init_ossl_rand(); diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index ac2199ac0..dc54e2435 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -11,7 +11,7 @@ */ #include "ossl.h" -#if defined(HAVE_CMS_SIGN) +#if !defined(OPENSSL_NO_CMS) /* * The CMS_ContentInfo is the primary data structure which this module creates and maintains * Is is called OpenSSL::CMS::ContentInfo in ruby. @@ -435,7 +435,7 @@ ossl_cms_s_sign(int argc, VALUE *argv, VALUE klass) } /* - * INIT + * INIT CMS interface */ void Init_ossl_cms(void) @@ -511,4 +511,11 @@ Init_ossl_cms(void) DefCMSConst(PARTIAL); } -#endif /* HAVE_CMS_SIGN */ +#else +/* empty init function for when OPENSSL_NO_CMS */ +void Init_ossl_cms(void) +{ + /* nothing */ +} + +#endif /* OPENSSL_NO_CMS */ From fa8efa32d28e10a3ad8da50342b1f28f18e0a299 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 28 Jul 2025 09:30:10 -0400 Subject: [PATCH 3/6] review: remove unnecessary hmac.h, sort alphabetically --- ext/openssl/ossl.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index d57fe2de2..efb1942b1 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -25,14 +25,13 @@ #include +#include #include #include #include #include #include #include -#include -#include #include #include #ifndef OPENSSL_NO_TS From b3d3d2c3e4e9abcedff6abca2483cb58ededa05f Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 28 Jul 2025 10:15:49 -0400 Subject: [PATCH 4/6] feat: stop using ePKCS7Error, make all error values static --- ext/openssl/ossl.h | 2 +- ext/openssl/ossl_cms.c | 18 +++++++++++------- ext/openssl/ossl_cms.h | 6 ------ ext/openssl/ossl_pkcs7.c | 2 +- ext/openssl/ossl_pkcs7.h | 1 - 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index efb1942b1..d40158b82 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -186,6 +186,7 @@ extern VALUE dOSSL; #include "ossl_bn.h" #include "ossl_cipher.h" #include "ossl_config.h" +#include "ossl_cms.h" #include "ossl_digest.h" #include "ossl_engine.h" #include "ossl_hmac.h" @@ -194,7 +195,6 @@ extern VALUE dOSSL; #include "ossl_ocsp.h" #include "ossl_pkcs12.h" #include "ossl_pkcs7.h" -#include "ossl_cms.h" #include "ossl_pkey.h" #include "ossl_provider.h" #include "ossl_rand.h" diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index dc54e2435..b0ecfa2a6 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -54,11 +54,14 @@ #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 VALUE cCMS; +static VALUE cCMSContentInfo; +static VALUE cCMSSignerInfo; +#if 0 +/* not yet implemented. */ +static VALUE cCMSRecipient; +#endif +static VALUE eCMSError; static void @@ -104,7 +107,7 @@ ossl_cmsci_to_pem(VALUE self) } if (!PEM_write_bio_CMS(out, cmsci)) { BIO_free(out); - ossl_raise(ePKCS7Error, NULL); + ossl_raise(eCMSError, NULL); } str = ossl_membio2str(out); @@ -423,7 +426,7 @@ ossl_cms_s_sign(int argc, VALUE *argv, VALUE klass) if(!(cms_cinfo = CMS_sign(x509, pkey, x509s, in, flg))){ BIO_free(in); sk_X509_pop_free(x509s, X509_free); - ossl_raise(ePKCS7Error, NULL); + ossl_raise(eCMSError, NULL); } SetCMSContentInfo(ret, cms_cinfo); ossl_cmsci_set_data(ret, data); @@ -470,6 +473,7 @@ Init_ossl_cms(void) rb_define_method(cCMSSignerInfo,"serial", ossl_cmssi_get_serial,0); #if 0 + /* not yet implemented. */ 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); diff --git a/ext/openssl/ossl_cms.h b/ext/openssl/ossl_cms.h index e0f03f5d7..23fc30d82 100644 --- a/ext/openssl/ossl_cms.h +++ b/ext/openssl/ossl_cms.h @@ -10,12 +10,6 @@ #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/ext/openssl/ossl_pkcs7.c b/ext/openssl/ossl_pkcs7.c index a863acfe4..4d719c96d 100644 --- a/ext/openssl/ossl_pkcs7.c +++ b/ext/openssl/ossl_pkcs7.c @@ -67,7 +67,7 @@ static VALUE cPKCS7; static VALUE cPKCS7Signer; static VALUE cPKCS7Recipient; -VALUE ePKCS7Error; +static VALUE ePKCS7Error; static void ossl_pkcs7_free(void *ptr) diff --git a/ext/openssl/ossl_pkcs7.h b/ext/openssl/ossl_pkcs7.h index 4cf3e420b..140fda183 100644 --- a/ext/openssl/ossl_pkcs7.h +++ b/ext/openssl/ossl_pkcs7.h @@ -10,7 +10,6 @@ #if !defined(_OSSL_PKCS7_H_) #define _OSSL_PKCS7_H_ -extern VALUE ePKCS7Error; VALUE ossl_pkcs7_new(PKCS7 *p7); void Init_ossl_pkcs7(void); From 540aad6cccce007e4b11d17760de1c0e07323389 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 28 Jul 2025 10:16:39 -0400 Subject: [PATCH 5/6] feat: keep reference to cms_content_info from cms_signer info, so that it will not get freed before thing contained in --- ext/openssl/ossl_cms.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index b0ecfa2a6..d102ccb41 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -302,6 +302,7 @@ ossl_cmssi_new(CMS_SignerInfo *cmssi) obj = NewCMSsi(cCMSSignerInfo); SetCMSsi(obj, cmssi); + rb_ivar_set(obj, rb_intern("cms"), cmssi); return obj; } From e33ce43143188e6d962805d42246ea3841d1240d Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Mon, 28 Jul 2025 10:17:25 -0400 Subject: [PATCH 6/6] feat: no need to test result of sk_* function, it will be correct if CMS_get0_SignerInfos function was successful --- ext/openssl/ossl_cms.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c index d102ccb41..1c8913c51 100644 --- a/ext/openssl/ossl_cms.c +++ b/ext/openssl/ossl_cms.c @@ -356,22 +356,18 @@ 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!"); - } + num = sk_CMS_SignerInfo_num(sk); ary = rb_ary_new2(num); for (i=0; i