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. 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. 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/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.c b/ext/openssl/ossl.c index 38e650e1a..b5b121e00 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1202,6 +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 5a15839cb..81b2a8387 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -24,6 +24,9 @@ #include #include #include +#if defined(HAVE_CMS_SIGN) +#include +#endif #include #include #include @@ -165,6 +168,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_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"); } 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 * diff --git a/ext/openssl/ossl_cms.c b/ext/openssl/ossl_cms.c new file mode 100644 index 000000000..fe3006f3d --- /dev/null +++ b/ext/openssl/ossl_cms.c @@ -0,0 +1,503 @@ +/* + * '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; + + 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(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 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/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); 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_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){ diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index 0053f2e37..e2cb18fc9 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"); } @@ -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) diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 935f61f0e..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($/) - remain = idx ? idx + $/.size : @wbuffer.length - 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 @@ -409,9 +404,7 @@ def puts(*args) end args.each{|arg| s << arg.to_s - if $/ && /\n\z/ !~ s - s << "\n" - end + s.sub!(/(?= 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_cms.rb b/test/test_cms.rb new file mode 100644 index 000000000..7f2eb66f1 --- /dev/null +++ b/test/test_cms.rb @@ -0,0 +1,96 @@ +# 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 + 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) 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] 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