diff options
60 files changed, 2614 insertions, 1179 deletions
diff --git a/.travis.yml b/.travis.yml index 032cc986..aec0e443 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ matrix: - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.0.1 - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.0.2 - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=openssl-1.1.0 - - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.4 - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.5 + - env: RUBY_VERSION=ruby-2.4 OPENSSL_VERSION=libressl-2.6 - language: ruby rvm: ruby-head before_install: @@ -1,26 +1,75 @@ -Version 2.1.0 -============= - -Compatibility notes -------------------- - -* Support for OpenSSL version 0.9.8 and 1.0.0 is completely removed. - -Supported platforms -------------------- - -* OpenSSL 1.0.1, 1.0.2, 1.1.0 -* LibreSSL 2.3, 2.4, 2.5 -* Ruby 2.3, 2.4 +Version 2.1.0.beta2 +=================== Notable changes --------------- -Removals --------- +* Support for OpenSSL versions before 1.0.1 and LibreSSL versions before 2.5 + is removed. + [[GitHub #86]](https://github.com/ruby/openssl/pull/86) +* OpenSSL::BN#negative?, #+@, and #-@ are added. +* OpenSSL::SSL::SSLSocket#connect raises a more informative exception when + certificate verification fails. + [[GitHub #99]](https://github.com/ruby/openssl/pull/99) +* OpenSSL::KDF module is newly added. In addition to PBKDF2-HMAC that has moved + from OpenSSL::PKCS5, scrypt and HKDF are supported. + [[GitHub #109]](https://github.com/ruby/openssl/pull/109) + [[GitHub #173]](https://github.com/ruby/openssl/pull/173) +* OpenSSL.fips_mode is added. We had the setter, but not the getter. + [[GitHub #125]](https://github.com/ruby/openssl/pull/125) +* OpenSSL::OCSP::Request#signed? is added. +* OpenSSL::ASN1 handles the indefinite length form better. OpenSSL::ASN1.decode + no longer wrongly treats the end-of-contents octets as part of the content. + OpenSSL::ASN1::ASN1Data#infinite_length is renamed to #indefinite_length. + [[GitHub #98]](https://github.com/ruby/openssl/pull/98) +* OpenSSL::X509::Name#add_entry now accepts two additional keyword arguments + 'loc' and 'set'. + [[GitHub #94]](https://github.com/ruby/openssl/issues/94) +* OpenSSL::SSL::SSLContext#min_version= and #max_version= are added to replace + #ssl_version= that was built on top of the deprecated OpenSSL C API. Use of + that method and the constant OpenSSL::SSL::SSLContext::METHODS is now + deprecated. + [[GitHub #142]](https://github.com/ruby/openssl/pull/142) +* OpenSSL::X509::Name#to_utf8 is added. + [[GitHub #26]](https://github.com/ruby/openssl/issues/26) + [[GitHub #143]](https://github.com/ruby/openssl/pull/143) +* OpenSSL::X509::{Extension,Attribute,Certificate,CRL,Revoked,Request} can be + compared with == operator. + [[GitHub #161]](https://github.com/ruby/openssl/pull/161) +* TLS Fallback Signaling Cipher Suite Value (SCSV) support is added. + [[GitHub #165]](https://github.com/ruby/openssl/pull/165) +* Build failure with OpenSSL 1.1 built with no-deprecated is fixed. + [[GitHub #160]](https://github.com/ruby/openssl/pull/160) +* OpenSSL::Buffering#write accepts an arbitrary number of arguments. + [[Feature #9323]](https://bugs.ruby-lang.org/issues/9323) + [[GitHub #162]](https://github.com/ruby/openssl/pull/162) +* OpenSSL::PKey::RSA#sign_pss and #verify_pss are added. They perform RSA-PSS + signature and verification. + [[GitHub #75]](https://github.com/ruby/openssl/issues/75) + [[GitHub #76]](https://github.com/ruby/openssl/pull/76) + [[GitHub #169]](https://github.com/ruby/openssl/pull/169) +* OpenSSL::SSL::SSLContext#add_certificate is added. + [[GitHub #167]](https://github.com/ruby/openssl/pull/167) + + +Version 2.0.6 +============= -Deprecations ------------- +Bug fixes +--------- + +* The session_remove_cb set to an OpenSSL::SSL::SSLContext is no longer called + during GC. +* A possible deadlock in OpenSSL::SSL::SSLSocket#sysread is fixed. + [[GitHub #139]](https://github.com/ruby/openssl/pull/139) +* OpenSSL::BN#hash could return an unnormalized fixnum value on Windows. + [[Bug #13877]](https://bugs.ruby-lang.org/issues/13877) +* OpenSSL::SSL::SSLSocket#sysread and #sysread_nonblock set the length of the + destination buffer String to 0 on error. + [[GitHub #153]](https://github.com/ruby/openssl/pull/153) +* Possible deadlock is fixed. This happened only when built with older versions + of OpenSSL (before 1.1.0) or LibreSSL. + [[GitHub #155]](https://github.com/ruby/openssl/pull/155) Version 2.0.5 @@ -175,7 +224,7 @@ Notable changes - A new option 'verify_hostname' is added to OpenSSL::SSL::SSLContext. When it is enabled, and the SNI hostname is also set, the hostname verification on the server certificate is automatically performed. It is now enabled by - OpenSSL::SSL::Context#set_params. + OpenSSL::SSL::SSLContext#set_params. [[GH ruby/openssl#60]](https://github.com/ruby/openssl/pull/60) Removals @@ -58,11 +58,12 @@ namespace :sync do paths = [ ["ext/openssl/", "ext/openssl/"], + ["lib/", "ext/openssl/lib/"], + ["sample/", "sample/openssl/"], + ["test/fixtures/", "test/openssl/fixtures/"], ["test/utils.rb", "test/openssl/"], ["test/ut_eof.rb", "test/openssl/"], ["test/test_*", "test/openssl/"], - ["lib/", "ext/openssl/lib/"], - ["sample/", "sample/openssl/"], ["History.md", "ext/openssl/"], ] paths.each do |src, dst| diff --git a/appveyor.yml b/appveyor.yml index 9ff363fc..18484fb9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,6 @@ install: $Env:openssl_dir = "C:\msys64\mingw64" } - ruby -v - - openssl version - rake install_dependencies build_script: - rake -rdevkit compile -- --with-openssl-dir=%openssl_dir% --enable-debug diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 0f099fc3..5212903b 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -104,11 +104,6 @@ end Logging::message "=== Checking for OpenSSL features... ===\n" # compile options - -# SSLv2 and SSLv3 may be removed in future versions of OpenSSL, and even macros -# like OPENSSL_NO_SSL2 may not be defined. -have_func("SSLv2_method") -have_func("SSLv3_method") have_func("RAND_egd") engines = %w{builtin_engines openbsd_dev_crypto dynamic 4758cca aep atalla chil cswift nuron sureware ubsec padlock capi gmp gost cryptodev aesni} diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index cc31f6ac..debd25ad 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -209,6 +209,10 @@ IMPL_PKEY_GETTER(EC_KEY, ec) # define X509_get0_notAfter(x) X509_get_notAfter(x) # define X509_CRL_get0_lastUpdate(x) X509_CRL_get_lastUpdate(x) # define X509_CRL_get0_nextUpdate(x) X509_CRL_get_nextUpdate(x) +# define X509_set1_notBefore(x, t) X509_set_notBefore(x, t) +# define X509_set1_notAfter(x, t) X509_set_notAfter(x, t) +# define X509_CRL_set1_lastUpdate(x, t) X509_CRL_set_lastUpdate(x, t) +# define X509_CRL_set1_nextUpdate(x, t) X509_CRL_set_nextUpdate(x, t) #endif #if !defined(HAVE_SSL_SESSION_GET_PROTOCOL_VERSION) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 6ec5e91c..245385e7 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -517,40 +517,53 @@ print_mem_leaks(VALUE self) /** * Stores locks needed for OpenSSL thread safety */ -static rb_nativethread_lock_t *ossl_locks; +struct CRYPTO_dynlock_value { + rb_nativethread_lock_t lock; + rb_nativethread_id_t owner; + size_t count; +}; static void -ossl_lock_unlock(int mode, rb_nativethread_lock_t *lock) +ossl_lock_init(struct CRYPTO_dynlock_value *l) { - if (mode & CRYPTO_LOCK) { - rb_nativethread_lock_lock(lock); - } else { - rb_nativethread_lock_unlock(lock); - } + rb_nativethread_lock_initialize(&l->lock); + l->count = 0; } static void -ossl_lock_callback(int mode, int type, const char *file, int line) +ossl_lock_unlock(int mode, struct CRYPTO_dynlock_value *l) { - ossl_lock_unlock(mode, &ossl_locks[type]); + if (mode & CRYPTO_LOCK) { + /* TODO: rb_nativethread_id_t is not necessarily compared with ==. */ + rb_nativethread_id_t tid = rb_nativethread_self(); + if (l->count && l->owner == tid) { + l->count++; + return; + } + rb_nativethread_lock_lock(&l->lock); + l->owner = tid; + l->count = 1; + } else { + if (!--l->count) + rb_nativethread_lock_unlock(&l->lock); + } } -struct CRYPTO_dynlock_value { - rb_nativethread_lock_t lock; -}; - static struct CRYPTO_dynlock_value * ossl_dyn_create_callback(const char *file, int line) { - struct CRYPTO_dynlock_value *dynlock = (struct CRYPTO_dynlock_value *)OPENSSL_malloc((int)sizeof(struct CRYPTO_dynlock_value)); - rb_nativethread_lock_initialize(&dynlock->lock); + /* Do not use xmalloc() here, since it may raise NoMemoryError */ + struct CRYPTO_dynlock_value *dynlock = + OPENSSL_malloc(sizeof(struct CRYPTO_dynlock_value)); + if (dynlock) + ossl_lock_init(dynlock); return dynlock; } static void ossl_dyn_lock_callback(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { - ossl_lock_unlock(mode, &l->lock); + ossl_lock_unlock(mode, l); } static void @@ -566,21 +579,22 @@ static void ossl_threadid_func(CRYPTO_THREADID *id) CRYPTO_THREADID_set_pointer(id, (void *)rb_nativethread_self()); } +static struct CRYPTO_dynlock_value *ossl_locks; + +static void +ossl_lock_callback(int mode, int type, const char *file, int line) +{ + ossl_lock_unlock(mode, &ossl_locks[type]); +} + static void Init_ossl_locks(void) { int i; int num_locks = CRYPTO_num_locks(); - if ((unsigned)num_locks >= INT_MAX / (int)sizeof(VALUE)) { - rb_raise(rb_eRuntimeError, "CRYPTO_num_locks() is too big: %d", num_locks); - } - ossl_locks = (rb_nativethread_lock_t *) OPENSSL_malloc(num_locks * (int)sizeof(rb_nativethread_lock_t)); - if (!ossl_locks) { - rb_raise(rb_eNoMemError, "CRYPTO_num_locks() is too big: %d", num_locks); - } - for (i = 0; i < num_locks; i++) { - rb_nativethread_lock_initialize(&ossl_locks[i]); - } + ossl_locks = ALLOC_N(struct CRYPTO_dynlock_value, num_locks); + for (i = 0; i < num_locks; i++) + ossl_lock_init(&ossl_locks[i]); CRYPTO_THREADID_set_callback(ossl_threadid_func); CRYPTO_set_locking_callback(ossl_lock_callback); @@ -1095,25 +1109,14 @@ Init_openssl(void) /* * Init all digests, ciphers */ - /* CRYPTO_malloc_init(); */ - /* ENGINE_load_builtin_engines(); */ +#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000 + if (!OPENSSL_init_ssl(0, NULL)) + rb_raise(rb_eRuntimeError, "OPENSSL_init_ssl"); +#else OpenSSL_add_ssl_algorithms(); OpenSSL_add_all_algorithms(); ERR_load_crypto_strings(); SSL_load_error_strings(); - - /* - * FIXME: - * On unload do: - */ -#if 0 - CONF_modules_unload(1); - destroy_ui_method(); - EVP_cleanup(); - ENGINE_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_remove_state(0); - ERR_free_strings(); #endif /* @@ -1135,7 +1138,11 @@ Init_openssl(void) /* * Version of OpenSSL the ruby OpenSSL extension is running with */ +#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000 + rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION))); +#else rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION))); +#endif /* * Version number of OpenSSL the ruby OpenSSL extension was built with diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index f08889b2..5a15839c 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -35,6 +35,11 @@ #if !defined(OPENSSL_NO_OCSP) # include <openssl/ocsp.h> #endif +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/dsa.h> +#include <openssl/evp.h> +#include <openssl/dh.h> /* * Common Module diff --git a/ext/openssl/ossl_bn.c b/ext/openssl/ossl_bn.c index 94ef6fd6..4666ce6c 100644 --- a/ext/openssl/ossl_bn.c +++ b/ext/openssl/ossl_bn.c @@ -979,20 +979,20 @@ static VALUE ossl_bn_hash(VALUE self) { BIGNUM *bn; - VALUE hash; + VALUE tmp, hash; unsigned char *buf; int len; GetBN(self, bn); len = BN_num_bytes(bn); - buf = xmalloc(len); + buf = ALLOCV(tmp, len); if (BN_bn2bin(bn, buf) != len) { - xfree(buf); - ossl_raise(eBNError, NULL); + ALLOCV_END(tmp); + ossl_raise(eBNError, "BN_bn2bin"); } - hash = INT2FIX(rb_memhash(buf, len)); - xfree(buf); + hash = ST2FIX(rb_memhash(buf, len)); + ALLOCV_END(tmp); return hash; } diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 21fcb1b6..3038a766 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -508,7 +508,7 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) StringValue(iv); GetCipher(self, ctx); - if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) + if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!iv_len) iv_len = EVP_CIPHER_CTX_iv_length(ctx); @@ -535,7 +535,7 @@ ossl_cipher_is_authenticated(VALUE self) GetCipher(self, ctx); - return (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse; + return (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse; } /* @@ -569,6 +569,8 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data) in_len = RSTRING_LEN(data); GetCipher(self, ctx); + if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) + ossl_raise(eCipherError, "AEAD not supported by this cipher"); if (!ossl_cipher_update_long(ctx, NULL, &out_len, in, in_len)) ossl_raise(eCipherError, "couldn't set additional authenticated data"); @@ -606,7 +608,7 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) GetCipher(self, ctx); - if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)) + if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) ossl_raise(eCipherError, "authentication tag not supported by this cipher"); ret = rb_str_new(NULL, tag_len); @@ -620,13 +622,11 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self) * call-seq: * cipher.auth_tag = string -> string * - * Sets the authentication tag to verify the contents of the - * ciphertext. The tag must be set after calling Cipher#decrypt, - * Cipher#key= and Cipher#iv=, but before assigning the associated - * authenticated data using Cipher#auth_data= and of course, before - * decrypting any of the ciphertext. After all decryption is - * performed, the tag is verified automatically in the call to - * Cipher#final. + * Sets the authentication tag to verify the integrity of the ciphertext. + * This can be called only when the cipher supports AE. The tag must be set + * after calling Cipher#decrypt, Cipher#key= and Cipher#iv=, but before + * calling Cipher#final. After all decryption is performed, the tag is + * verified automatically in the call to Cipher#final. * * For OCB mode, the tag length must be supplied with #auth_tag_len= * beforehand. @@ -643,7 +643,7 @@ ossl_cipher_set_auth_tag(VALUE self, VALUE vtag) tag_len = RSTRING_LENINT(vtag); GetCipher(self, ctx); - if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)) + if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) ossl_raise(eCipherError, "authentication tag not supported by this cipher"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, tag)) @@ -670,7 +670,7 @@ ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen) EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); - if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)) + if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) ossl_raise(eCipherError, "AEAD not supported by this cipher"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL)) @@ -697,7 +697,7 @@ ossl_cipher_set_iv_length(VALUE self, VALUE iv_length) EVP_CIPHER_CTX *ctx; GetCipher(self, ctx); - if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)) + if (!(EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER)) ossl_raise(eCipherError, "cipher does not support AEAD"); if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL)) @@ -788,7 +788,7 @@ ossl_cipher_iv_length(VALUE self) int len = 0; GetCipher(self, ctx); - if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) + if (EVP_CIPHER_flags(EVP_CIPHER_CTX_cipher(ctx)) & EVP_CIPH_FLAG_AEAD_CIPHER) len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx); if (!len) len = EVP_CIPHER_CTX_iv_length(ctx); diff --git a/ext/openssl/ossl_engine.c b/ext/openssl/ossl_engine.c index d69b5dca..5ca0d4ca 100644 --- a/ext/openssl/ossl_engine.c +++ b/ext/openssl/ossl_engine.c @@ -46,13 +46,25 @@ VALUE eEngineError; /* * Private */ -#define OSSL_ENGINE_LOAD_IF_MATCH(x) \ +#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000 +#define OSSL_ENGINE_LOAD_IF_MATCH(engine_name, x) \ do{\ - if(!strcmp(#x, RSTRING_PTR(name))){\ - ENGINE_load_##x();\ + if(!strcmp(#engine_name, RSTRING_PTR(name))){\ + if (OPENSSL_init_crypto(OPENSSL_INIT_ENGINE_##x, NULL))\ + return Qtrue;\ + else\ + ossl_raise(eEngineError, "OPENSSL_init_crypto"); \ + }\ +}while(0) +#else +#define OSSL_ENGINE_LOAD_IF_MATCH(engine_name, x) \ +do{\ + if(!strcmp(#engine_name, RSTRING_PTR(name))){\ + ENGINE_load_##engine_name();\ return Qtrue;\ }\ }while(0) +#endif static void ossl_engine_free(void *engine) @@ -94,55 +106,55 @@ ossl_engine_s_load(int argc, VALUE *argv, VALUE klass) StringValueCStr(name); #ifndef OPENSSL_NO_STATIC_ENGINE #if HAVE_ENGINE_LOAD_DYNAMIC - OSSL_ENGINE_LOAD_IF_MATCH(dynamic); + OSSL_ENGINE_LOAD_IF_MATCH(dynamic, DYNAMIC); #endif #if HAVE_ENGINE_LOAD_4758CCA - OSSL_ENGINE_LOAD_IF_MATCH(4758cca); + OSSL_ENGINE_LOAD_IF_MATCH(4758cca, 4758CCA); #endif #if HAVE_ENGINE_LOAD_AEP - OSSL_ENGINE_LOAD_IF_MATCH(aep); + OSSL_ENGINE_LOAD_IF_MATCH(aep, AEP); #endif #if HAVE_ENGINE_LOAD_ATALLA - OSSL_ENGINE_LOAD_IF_MATCH(atalla); + OSSL_ENGINE_LOAD_IF_MATCH(atalla, ATALLA); #endif #if HAVE_ENGINE_LOAD_CHIL - OSSL_ENGINE_LOAD_IF_MATCH(chil); + OSSL_ENGINE_LOAD_IF_MATCH(chil, CHIL); #endif #if HAVE_ENGINE_LOAD_CSWIFT - OSSL_ENGINE_LOAD_IF_MATCH(cswift); + OSSL_ENGINE_LOAD_IF_MATCH(cswift, CSWIFT); #endif #if HAVE_ENGINE_LOAD_NURON - OSSL_ENGINE_LOAD_IF_MATCH(nuron); + OSSL_ENGINE_LOAD_IF_MATCH(nuron, NURON); #endif #if HAVE_ENGINE_LOAD_SUREWARE - OSSL_ENGINE_LOAD_IF_MATCH(sureware); + OSSL_ENGINE_LOAD_IF_MATCH(sureware, SUREWARE); #endif #if HAVE_ENGINE_LOAD_UBSEC - OSSL_ENGINE_LOAD_IF_MATCH(ubsec); + OSSL_ENGINE_LOAD_IF_MATCH(ubsec, UBSEC); #endif #if HAVE_ENGINE_LOAD_PADLOCK - OSSL_ENGINE_LOAD_IF_MATCH(padlock); + OSSL_ENGINE_LOAD_IF_MATCH(padlock, PADLOCK); #endif #if HAVE_ENGINE_LOAD_CAPI - OSSL_ENGINE_LOAD_IF_MATCH(capi); + OSSL_ENGINE_LOAD_IF_MATCH(capi, CAPI); #endif #if HAVE_ENGINE_LOAD_GMP - OSSL_ENGINE_LOAD_IF_MATCH(gmp); + OSSL_ENGINE_LOAD_IF_MATCH(gmp, GMP); #endif #if HAVE_ENGINE_LOAD_GOST - OSSL_ENGINE_LOAD_IF_MATCH(gost); + OSSL_ENGINE_LOAD_IF_MATCH(gost, GOST); #endif #if HAVE_ENGINE_LOAD_CRYPTODEV - OSSL_ENGINE_LOAD_IF_MATCH(cryptodev); + OSSL_ENGINE_LOAD_IF_MATCH(cryptodev, CRYPTODEV); #endif #if HAVE_ENGINE_LOAD_AESNI - OSSL_ENGINE_LOAD_IF_MATCH(aesni); + OSSL_ENGINE_LOAD_IF_MATCH(aesni, AESNI); #endif #endif #ifdef HAVE_ENGINE_LOAD_OPENBSD_DEV_CRYPTO - OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto); + OSSL_ENGINE_LOAD_IF_MATCH(openbsd_dev_crypto, OPENBSD_DEV_CRYPTO); #endif - OSSL_ENGINE_LOAD_IF_MATCH(openssl); + OSSL_ENGINE_LOAD_IF_MATCH(openssl, OPENSSL); rb_warning("no such builtin loader for `%"PRIsVALUE"'", name); return Qnil; #endif /* HAVE_ENGINE_LOAD_BUILTIN_ENGINES */ @@ -160,7 +172,9 @@ ossl_engine_s_load(int argc, VALUE *argv, VALUE klass) static VALUE ossl_engine_s_cleanup(VALUE self) { +#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000 ENGINE_cleanup(); +#endif return Qnil; } diff --git a/ext/openssl/ossl_kdf.c b/ext/openssl/ossl_kdf.c index 9fa42e17..ee124718 100644 --- a/ext/openssl/ossl_kdf.c +++ b/ext/openssl/ossl_kdf.c @@ -3,6 +3,9 @@ * Copyright (C) 2007, 2017 Ruby/OpenSSL Project Authors */ #include "ossl.h" +#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +# include <openssl/kdf.h> +#endif static VALUE mKDF, eKDF; @@ -138,6 +141,97 @@ kdf_scrypt(int argc, VALUE *argv, VALUE self) } #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) +/* + * call-seq: + * KDF.hkdf(ikm, salt:, info:, length:, hash:) -> String + * + * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) as specified in + * {RFC 5869}[https://tools.ietf.org/html/rfc5869]. + * + * New in OpenSSL 1.1.0. + * + * === Parameters + * _ikm_:: + * The input keying material. + * _salt_:: + * The salt. + * _info_:: + * The context and application specific information. + * _length_:: + * The output length in octets. Must be <= <tt>255 * HashLen</tt>, where + * HashLen is the length of the hash function output in octets. + * _hash_:: + * The hash function. + */ +static VALUE +kdf_hkdf(int argc, VALUE *argv, VALUE self) +{ + VALUE ikm, salt, info, opts, kwargs[4], str; + static ID kwargs_ids[4]; + int saltlen, ikmlen, infolen; + size_t len; + const EVP_MD *md; + EVP_PKEY_CTX *pctx; + + if (!kwargs_ids[0]) { + kwargs_ids[0] = rb_intern_const("salt"); + kwargs_ids[1] = rb_intern_const("info"); + kwargs_ids[2] = rb_intern_const("length"); + kwargs_ids[3] = rb_intern_const("hash"); + } + rb_scan_args(argc, argv, "1:", &ikm, &opts); + rb_get_kwargs(opts, kwargs_ids, 4, 0, kwargs); + + StringValue(ikm); + ikmlen = RSTRING_LENINT(ikm); + salt = StringValue(kwargs[0]); + saltlen = RSTRING_LENINT(salt); + info = StringValue(kwargs[1]); + infolen = RSTRING_LENINT(info); + len = (size_t)NUM2LONG(kwargs[2]); + if (len > LONG_MAX) + rb_raise(rb_eArgError, "length must be non-negative"); + md = ossl_evp_get_digestbyname(kwargs[3]); + + str = rb_str_new(NULL, (long)len); + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (!pctx) + ossl_raise(eKDF, "EVP_PKEY_CTX_new_id"); + if (EVP_PKEY_derive_init(pctx) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive_init"); + } + if (EVP_PKEY_CTX_set_hkdf_md(pctx, md) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_md"); + } + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, (unsigned char *)RSTRING_PTR(salt), + saltlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_salt"); + } + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, (unsigned char *)RSTRING_PTR(ikm), + ikmlen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_key"); + } + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, (unsigned char *)RSTRING_PTR(info), + infolen) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_CTX_set_hkdf_info"); + } + if (EVP_PKEY_derive(pctx, (unsigned char *)RSTRING_PTR(str), &len) <= 0) { + EVP_PKEY_CTX_free(pctx); + ossl_raise(eKDF, "EVP_PKEY_derive"); + } + rb_str_set_len(str, (long)len); + EVP_PKEY_CTX_free(pctx); + + return str; +} +#endif + void Init_ossl_kdf(void) { @@ -162,6 +256,7 @@ Init_ossl_kdf(void) * * PKCS #5 PBKDF2 (Password-Based Key Derivation Function 2) in * combination with HMAC * * scrypt + * * HKDF * * == Examples * === Generating a 128 bit key for a Cipher (e.g. AES) @@ -218,4 +313,7 @@ Init_ossl_kdf(void) #if defined(HAVE_EVP_PBE_SCRYPT) rb_define_module_function(mKDF, "scrypt", kdf_scrypt, -1); #endif +#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + rb_define_module_function(mKDF, "hkdf", kdf_hkdf, -1); +#endif } diff --git a/ext/openssl/ossl_ns_spki.c b/ext/openssl/ossl_ns_spki.c index f17b9509..6f61e61b 100644 --- a/ext/openssl/ossl_ns_spki.c +++ b/ext/openssl/ossl_ns_spki.c @@ -208,12 +208,13 @@ static VALUE ossl_spki_set_public_key(VALUE self, VALUE key) { NETSCAPE_SPKI *spki; + EVP_PKEY *pkey; GetSPKI(self, spki); - if (!NETSCAPE_SPKI_set_pubkey(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */ - ossl_raise(eSPKIError, NULL); - } - + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); + if (!NETSCAPE_SPKI_set_pubkey(spki, pkey)) + ossl_raise(eSPKIError, "NETSCAPE_SPKI_set_pubkey"); return key; } @@ -307,17 +308,20 @@ static VALUE ossl_spki_verify(VALUE self, VALUE key) { NETSCAPE_SPKI *spki; + EVP_PKEY *pkey; GetSPKI(self, spki); - switch (NETSCAPE_SPKI_verify(spki, GetPKeyPtr(key))) { /* NO NEED TO DUP */ - case 0: + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); + switch (NETSCAPE_SPKI_verify(spki, pkey)) { + case 0: + ossl_clear_error(); return Qfalse; - case 1: + case 1: return Qtrue; - default: - ossl_raise(eSPKIError, NULL); + default: + ossl_raise(eSPKIError, "NETSCAPE_SPKI_verify"); } - return Qnil; /* dummy */ } /* Document-class: OpenSSL::Netscape::SPKI diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 23e21154..2b96ece5 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -163,8 +163,8 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) return ossl_pkey_new(pkey); } -static void -pkey_check_public_key(EVP_PKEY *pkey) +void +ossl_pkey_check_public_key(const EVP_PKEY *pkey) { void *ptr; const BIGNUM *n, *e, *pubkey; @@ -172,7 +172,8 @@ pkey_check_public_key(EVP_PKEY *pkey) if (EVP_PKEY_missing_parameters(pkey)) ossl_raise(ePKeyError, "parameters missing"); - ptr = EVP_PKEY_get0(pkey); + /* OpenSSL < 1.1.0 takes non-const pointer */ + ptr = EVP_PKEY_get0((EVP_PKEY *)pkey); switch (EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: RSA_get0_key(ptr, &n, &e, NULL); @@ -352,7 +353,7 @@ ossl_pkey_verify(VALUE self, VALUE digest, VALUE sig, VALUE data) int siglen, result; GetPKey(self, pkey); - pkey_check_public_key(pkey); + ossl_pkey_check_public_key(pkey); md = ossl_evp_get_digestbyname(digest); StringValue(sig); siglen = RSTRING_LENINT(sig); diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h index a87472ad..2b17bf53 100644 --- a/ext/openssl/ossl_pkey.h +++ b/ext/openssl/ossl_pkey.h @@ -44,6 +44,7 @@ int ossl_generate_cb_2(int p, int n, BN_GENCB *cb); void ossl_generate_cb_stop(void *ptr); VALUE ossl_pkey_new(EVP_PKEY *); +void ossl_pkey_check_public_key(const EVP_PKEY *); EVP_PKEY *GetPKeyPtr(VALUE); EVP_PKEY *DupPKeyPtr(VALUE); EVP_PKEY *GetPrivPKeyPtr(VALUE); diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c index 26397bd0..4800fb27 100644 --- a/ext/openssl/ossl_pkey_rsa.c +++ b/ext/openssl/ossl_pkey_rsa.c @@ -538,6 +538,196 @@ ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self) /* * call-seq: + * rsa.sign_pss(digest, data, salt_length:, mgf1_hash:) -> String + * + * Signs _data_ using the Probabilistic Signature Scheme (RSA-PSS) and returns + * the calculated signature. + * + * RSAError will be raised if an error occurs. + * + * See #verify_pss for the verification operation. + * + * === Parameters + * _digest_:: + * A String containing the message digest algorithm name. + * _data_:: + * A String. The data to be signed. + * _salt_length_:: + * The length in octets of the salt. Two special values are reserved: + * +:digest+ means the digest length, and +:max+ means the maximum possible + * length for the combination of the private key and the selected message + * digest algorithm. + * _mgf1_hash_:: + * The hash algorithm used in MGF1 (the currently supported mask generation + * function (MGF)). + * + * === Example + * data = "Sign me!" + * pkey = OpenSSL::PKey::RSA.new(2048) + * signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256") + * pub_key = pkey.public_key + * puts pub_key.verify_pss("SHA256", signature, data, + * salt_length: :auto, mgf1_hash: "SHA256") # => true + */ +static VALUE +ossl_rsa_sign_pss(int argc, VALUE *argv, VALUE self) +{ + VALUE digest, data, options, kwargs[2], signature; + static ID kwargs_ids[2]; + EVP_PKEY *pkey; + EVP_PKEY_CTX *pkey_ctx; + const EVP_MD *md, *mgf1md; + EVP_MD_CTX *md_ctx; + size_t buf_len; + int salt_len; + + if (!kwargs_ids[0]) { + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); + } + rb_scan_args(argc, argv, "2:", &digest, &data, &options); + rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); + if (kwargs[0] == ID2SYM(rb_intern("max"))) + salt_len = -2; /* RSA_PSS_SALTLEN_MAX_SIGN */ + else if (kwargs[0] == ID2SYM(rb_intern("digest"))) + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + else + salt_len = NUM2INT(kwargs[0]); + mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + + pkey = GetPrivPKeyPtr(self); + buf_len = EVP_PKEY_size(pkey); + md = ossl_evp_get_digestbyname(digest); + StringValue(data); + signature = rb_str_new(NULL, (long)buf_len); + + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) + goto err; + + if (EVP_DigestSignInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) + goto err; + + if (EVP_DigestSignUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) + goto err; + + if (EVP_DigestSignFinal(md_ctx, (unsigned char *)RSTRING_PTR(signature), &buf_len) != 1) + goto err; + + rb_str_set_len(signature, (long)buf_len); + + EVP_MD_CTX_free(md_ctx); + return signature; + + err: + EVP_MD_CTX_free(md_ctx); + ossl_raise(eRSAError, NULL); +} + +/* + * call-seq: + * rsa.verify_pss(digest, signature, data, salt_length:, mgf1_hash:) -> true | false + * + * Verifies _data_ using the Probabilistic Signature Scheme (RSA-PSS). + * + * The return value is +true+ if the signature is valid, +false+ otherwise. + * RSAError will be raised if an error occurs. + * + * See #sign_pss for the signing operation and an example code. + * + * === Parameters + * _digest_:: + * A String containing the message digest algorithm name. + * _data_:: + * A String. The data to be signed. + * _salt_length_:: + * The length in octets of the salt. Two special values are reserved: + * +:digest+ means the digest length, and +:auto+ means automatically + * determining the length based on the signature. + * _mgf1_hash_:: + * The hash algorithm used in MGF1. + */ +static VALUE +ossl_rsa_verify_pss(int argc, VALUE *argv, VALUE self) +{ + VALUE digest, signature, data, options, kwargs[2]; + static ID kwargs_ids[2]; + EVP_PKEY *pkey; + EVP_PKEY_CTX *pkey_ctx; + const EVP_MD *md, *mgf1md; + EVP_MD_CTX *md_ctx; + int result, salt_len; + + if (!kwargs_ids[0]) { + kwargs_ids[0] = rb_intern_const("salt_length"); + kwargs_ids[1] = rb_intern_const("mgf1_hash"); + } + rb_scan_args(argc, argv, "3:", &digest, &signature, &data, &options); + rb_get_kwargs(options, kwargs_ids, 2, 0, kwargs); + if (kwargs[0] == ID2SYM(rb_intern("auto"))) + salt_len = -2; /* RSA_PSS_SALTLEN_AUTO */ + else if (kwargs[0] == ID2SYM(rb_intern("digest"))) + salt_len = -1; /* RSA_PSS_SALTLEN_DIGEST */ + else + salt_len = NUM2INT(kwargs[0]); + mgf1md = ossl_evp_get_digestbyname(kwargs[1]); + + GetPKey(self, pkey); + md = ossl_evp_get_digestbyname(digest); + StringValue(signature); + StringValue(data); + + md_ctx = EVP_MD_CTX_new(); + if (!md_ctx) + goto err; + + if (EVP_DigestVerifyInit(md_ctx, &pkey_ctx, md, NULL, pkey) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, salt_len) != 1) + goto err; + + if (EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1md) != 1) + goto err; + + if (EVP_DigestVerifyUpdate(md_ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) + goto err; + + result = EVP_DigestVerifyFinal(md_ctx, + (unsigned char *)RSTRING_PTR(signature), + RSTRING_LEN(signature)); + + switch (result) { + case 0: + ossl_clear_error(); + EVP_MD_CTX_free(md_ctx); + return Qfalse; + case 1: + EVP_MD_CTX_free(md_ctx); + return Qtrue; + default: + goto err; + } + + err: + EVP_MD_CTX_free(md_ctx); + ossl_raise(eRSAError, NULL); +} + +/* + * call-seq: * rsa.params => hash * * THIS METHOD IS INSECURE, PRIVATE INFORMATION CAN LEAK OUT!!! @@ -731,6 +921,8 @@ Init_ossl_rsa(void) rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1); rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1); rb_define_method(cRSA, "private_decrypt", ossl_rsa_private_decrypt, -1); + rb_define_method(cRSA, "sign_pss", ossl_rsa_sign_pss, -1); + rb_define_method(cRSA, "verify_pss", ossl_rsa_verify_pss, -1); DEF_OSSL_PKEY_BN(cRSA, rsa, n); DEF_OSSL_PKEY_BN(cRSA, rsa, e); diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 51418410..85ef8709 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -32,7 +32,7 @@ VALUE cSSLSocket; static VALUE eSSLErrorWaitReadable; static VALUE eSSLErrorWaitWritable; -static ID ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback, +static ID id_call, ID_callback_state, id_tmp_dh_callback, id_tmp_ecdh_callback, id_npn_protocols_encoded; static VALUE sym_exception, sym_wait_readable, sym_wait_writable; @@ -46,44 +46,6 @@ static ID id_i_cert_store, id_i_ca_file, id_i_ca_path, id_i_verify_mode, id_i_verify_hostname; static ID id_i_io, id_i_context, id_i_hostname; -/* - * SSLContext class - */ -static const struct { - const char *name; - const SSL_METHOD *(*func)(void); - int version; -} ossl_ssl_method_tab[] = { -#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION) -#define OSSL_SSL_METHOD_ENTRY(name, version) \ - { #name, TLS_method, version }, \ - { #name"_server", TLS_server_method, version }, \ - { #name"_client", TLS_client_method, version } -#else -#define OSSL_SSL_METHOD_ENTRY(name, version) \ - { #name, name##_method, version }, \ - { #name"_server", name##_server_method, version }, \ - { #name"_client", name##_client_method, version } -#endif -#if !defined(OPENSSL_NO_SSL2) && !defined(OPENSSL_NO_SSL2_METHOD) && defined(HAVE_SSLV2_METHOD) - OSSL_SSL_METHOD_ENTRY(SSLv2, SSL2_VERSION), -#endif -#if !defined(OPENSSL_NO_SSL3) && !defined(OPENSSL_NO_SSL3_METHOD) && defined(HAVE_SSLV3_METHOD) - OSSL_SSL_METHOD_ENTRY(SSLv3, SSL3_VERSION), -#endif -#if !defined(OPENSSL_NO_TLS1) && !defined(OPENSSL_NO_TLS1_METHOD) - OSSL_SSL_METHOD_ENTRY(TLSv1, TLS1_VERSION), -#endif -#if !defined(OPENSSL_NO_TLS1_1) && !defined(OPENSSL_NO_TLS1_1_METHOD) - OSSL_SSL_METHOD_ENTRY(TLSv1_1, TLS1_1_VERSION), -#endif -#if !defined(OPENSSL_NO_TLS1_2) && !defined(OPENSSL_NO_TLS1_2_METHOD) - OSSL_SSL_METHOD_ENTRY(TLSv1_2, TLS1_2_VERSION), -#endif - OSSL_SSL_METHOD_ENTRY(SSLv23, 0), -#undef OSSL_SSL_METHOD_ENTRY -}; - static int ossl_ssl_ex_vcb_idx; static int ossl_ssl_ex_ptr_idx; static int ossl_sslctx_ex_ptr_idx; @@ -121,7 +83,11 @@ ossl_sslctx_s_alloc(VALUE klass) VALUE obj; obj = TypedData_Wrap_Struct(klass, &ossl_sslctx_type, 0); +#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) + ctx = SSL_CTX_new(TLS_method()); +#else ctx = SSL_CTX_new(SSLv23_method()); +#endif if (!ctx) { ossl_raise(eSSLError, "SSL_CTX_new"); } @@ -144,49 +110,89 @@ ossl_sslctx_s_alloc(VALUE klass) return obj; } +static int +parse_proto_version(VALUE str) +{ + int i; + static const struct { + const char *name; + int version; + } map[] = { + { "SSL2", SSL2_VERSION }, + { "SSL3", SSL3_VERSION }, + { "TLS1", TLS1_VERSION }, + { "TLS1_1", TLS1_1_VERSION }, + { "TLS1_2", TLS1_2_VERSION }, +#ifdef TLS1_3_VERSION + { "TLS1_3", TLS1_3_VERSION }, +#endif + }; + + if (NIL_P(str)) + return 0; + if (RB_INTEGER_TYPE_P(str)) + return NUM2INT(str); + + if (SYMBOL_P(str)) + str = rb_sym2str(str); + StringValue(str); + for (i = 0; i < numberof(map); i++) + if (!strncmp(map[i].name, RSTRING_PTR(str), RSTRING_LEN(str))) + return map[i].version; + rb_raise(rb_eArgError, "unrecognized version %+"PRIsVALUE, str); +} + /* * call-seq: - * ctx.ssl_version = :TLSv1 - * ctx.ssl_version = "SSLv23_client" + * ctx.set_minmax_proto_version(min, max) -> nil * - * Sets the SSL/TLS protocol version for the context. This forces connections to - * use only the specified protocol version. - * - * You can get a list of valid versions with OpenSSL::SSL::SSLContext::METHODS + * Sets the minimum and maximum supported protocol versions. See #min_version= + * and #max_version=. */ static VALUE -ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method) +ossl_sslctx_set_minmax_proto_version(VALUE self, VALUE min_v, VALUE max_v) { SSL_CTX *ctx; - const char *s; - VALUE m = ssl_method; - int i; + int min, max; GetSSLCTX(self, ctx); - if (RB_TYPE_P(ssl_method, T_SYMBOL)) - m = rb_sym2str(ssl_method); - s = StringValueCStr(m); - for (i = 0; i < numberof(ossl_ssl_method_tab); i++) { - if (strcmp(ossl_ssl_method_tab[i].name, s) == 0) { -#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION) - int version = ossl_ssl_method_tab[i].version; -#endif - const SSL_METHOD *method = ossl_ssl_method_tab[i].func(); - - if (SSL_CTX_set_ssl_version(ctx, method) != 1) - ossl_raise(eSSLError, "SSL_CTX_set_ssl_version"); + min = parse_proto_version(min_v); + max = parse_proto_version(max_v); + +#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION + if (!SSL_CTX_set_min_proto_version(ctx, min)) + ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version"); + if (!SSL_CTX_set_max_proto_version(ctx, max)) + ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version"); +#else + { + unsigned long sum = 0, opts = 0; + int i; + static const struct { + int ver; + unsigned long opts; + } options_map[] = { + { SSL2_VERSION, SSL_OP_NO_SSLv2 }, + { SSL3_VERSION, SSL_OP_NO_SSLv3 }, + { TLS1_VERSION, SSL_OP_NO_TLSv1 }, + { TLS1_1_VERSION, SSL_OP_NO_TLSv1_1 }, + { TLS1_2_VERSION, SSL_OP_NO_TLSv1_2 }, +# if defined(TLS1_3_VERSION) + { TLS1_3_VERSION, SSL_OP_NO_TLSv1_3 }, +# endif + }; -#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION) - if (!SSL_CTX_set_min_proto_version(ctx, version)) - ossl_raise(eSSLError, "SSL_CTX_set_min_proto_version"); - if (!SSL_CTX_set_max_proto_version(ctx, version)) - ossl_raise(eSSLError, "SSL_CTX_set_max_proto_version"); -#endif - return ssl_method; - } + 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) + opts |= options_map[i].opts; + } + SSL_CTX_clear_options(ctx, sum); + SSL_CTX_set_options(ctx, opts); } +#endif - ossl_raise(rb_eArgError, "unknown SSL method `%"PRIsVALUE"'.", m); + return Qnil; } static VALUE @@ -199,7 +205,7 @@ ossl_call_client_cert_cb(VALUE obj) if (NIL_P(cb)) return Qnil; - ary = rb_funcall(cb, rb_intern("call"), 1, obj); + ary = rb_funcallv(cb, id_call, 1, &obj); Check_Type(ary, T_ARRAY); GetX509CertPtr(cert = rb_ary_entry(ary, 0)); GetPrivPKeyPtr(key = rb_ary_entry(ary, 1)); @@ -242,8 +248,8 @@ ossl_call_tmp_dh_callback(struct tmp_dh_callback_args *args) cb = rb_funcall(args->ssl_obj, args->id, 0); if (NIL_P(cb)) return NULL; - dh = rb_funcall(cb, rb_intern("call"), 3, - args->ssl_obj, INT2NUM(args->is_export), INT2NUM(args->keylength)); + dh = rb_funcall(cb, id_call, 3, args->ssl_obj, INT2NUM(args->is_export), + INT2NUM(args->keylength)); pkey = GetPKeyPtr(dh); if (EVP_PKEY_base_id(pkey) != args->type) return NULL; @@ -368,12 +374,11 @@ ossl_call_session_get_cb(VALUE ary) cb = rb_funcall(ssl_obj, rb_intern("session_get_cb"), 0); if (NIL_P(cb)) return Qnil; - return rb_funcall(cb, rb_intern("call"), 1, ary); + return rb_funcallv(cb, id_call, 1, &ary); } -/* this method is currently only called for servers (in OpenSSL <= 0.9.8e) */ static SSL_SESSION * -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER) ossl_sslctx_session_get_cb(SSL *ssl, const unsigned char *buf, int len, int *copy) #else ossl_sslctx_session_get_cb(SSL *ssl, unsigned char *buf, int len, int *copy) @@ -414,7 +419,7 @@ ossl_call_session_new_cb(VALUE ary) cb = rb_funcall(ssl_obj, rb_intern("session_new_cb"), 0); if (NIL_P(cb)) return Qnil; - return rb_funcall(cb, rb_intern("call"), 1, ary); + return rb_funcallv(cb, id_call, 1, &ary); } /* return 1 normal. return 0 removes the session */ @@ -461,7 +466,7 @@ ossl_call_session_remove_cb(VALUE ary) cb = rb_attr_get(sslctx_obj, id_i_session_remove_cb); if (NIL_P(cb)) return Qnil; - return rb_funcall(cb, rb_intern("call"), 1, ary); + return rb_funcallv(cb, id_call, 1, &ary); } static void @@ -470,6 +475,13 @@ ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) VALUE ary, sslctx_obj, sess_obj; int state = 0; + /* + * This callback is also called for all sessions in the internal store + * when SSL_CTX_free() is called. + */ + if (rb_during_gc()) + return; + OSSL_Debug("SSL SESSION remove callback entered"); sslctx_obj = (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); @@ -520,7 +532,7 @@ ossl_call_servername_cb(VALUE ary) cb = rb_attr_get(sslctx_obj, id_i_servername_cb); if (NIL_P(cb)) return Qnil; - ret_obj = rb_funcall(cb, rb_intern("call"), 1, ary); + ret_obj = rb_funcallv(cb, id_call, 1, &ary); if (rb_obj_is_kind_of(ret_obj, cSSLContext)) { SSL *ssl; SSL_CTX *ctx2; @@ -572,10 +584,10 @@ ssl_renegotiation_cb(const SSL *ssl) cb = rb_attr_get(sslctx_obj, id_i_renegotiation_cb); if (NIL_P(cb)) return; - (void) rb_funcall(cb, rb_intern("call"), 1, ssl_obj); + rb_funcallv(cb, id_call, 1, &ssl_obj); } -#if defined(HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB) || \ +#if !defined(OPENSSL_NO_NEXTPROTONEG) || \ defined(HAVE_SSL_CTX_SET_ALPN_SELECT_CB) static VALUE ssl_npn_encode_protocol_i(VALUE cur, VALUE encoded) @@ -622,7 +634,7 @@ npn_select_cb_common_i(VALUE tmp) in += l; } - selected = rb_funcall(args->cb, rb_intern("call"), 1, protocols); + selected = rb_funcallv(args->cb, id_call, 1, &protocols); StringValue(selected); len = RSTRING_LEN(selected); if (len < 1 || len >= 256) { @@ -660,7 +672,7 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, } #endif -#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB +#ifndef OPENSSL_NO_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) @@ -720,7 +732,11 @@ ossl_sslctx_get_options(VALUE self) { SSL_CTX *ctx; GetSSLCTX(self, ctx); - return LONG2NUM(SSL_CTX_get_options(ctx)); + /* + * Do explicit cast because SSL_CTX_get_options() returned (signed) long in + * OpenSSL before 1.1.0. + */ + return ULONG2NUM((unsigned long)SSL_CTX_get_options(ctx)); } /* @@ -739,7 +755,7 @@ ossl_sslctx_set_options(VALUE self, VALUE options) if (NIL_P(options)) { SSL_CTX_set_options(ctx, SSL_OP_ALL); } else { - SSL_CTX_set_options(ctx, NUM2LONG(options)); + SSL_CTX_set_options(ctx, NUM2ULONG(options)); } return self; @@ -874,7 +890,7 @@ ossl_sslctx_setup(VALUE self) val = rb_attr_get(self, id_i_verify_depth); if(!NIL_P(val)) SSL_CTX_set_verify_depth(ctx, NUM2INT(val)); -#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB +#ifndef OPENSSL_NO_NEXTPROTONEG val = rb_attr_get(self, id_i_npn_protocols); if (!NIL_P(val)) { VALUE encoded = ssl_encode_npn_protocols(val); @@ -1176,6 +1192,134 @@ ossl_sslctx_set_security_level(VALUE self, VALUE value) return value; } +#ifdef SSL_MODE_SEND_FALLBACK_SCSV +/* + * call-seq: + * ctx.enable_fallback_scsv() => nil + * + * Activate TLS_FALLBACK_SCSV for this context. + * See RFC 7507. + */ +static VALUE +ossl_sslctx_enable_fallback_scsv(VALUE self) +{ + SSL_CTX *ctx; + + GetSSLCTX(self, ctx); + SSL_CTX_set_mode(ctx, SSL_MODE_SEND_FALLBACK_SCSV); + + return Qnil; +} +#endif + +/* + * call-seq: + * ctx.add_certificate(certiticate, pkey [, extra_certs]) -> self + * + * Adds a certificate to the context. _pkey_ must be a corresponding private + * key with _certificate_. + * + * Multiple certificates with different public key type can be added by + * repeated calls of this method, and OpenSSL will choose the most appropriate + * certificate during the handshake. + * + * #cert=, #key=, and #extra_chain_cert= are old accessor methods for setting + * certificate and internally call this method. + * + * === Parameters + * _certificate_:: + * A certificate. An instance of OpenSSL::X509::Certificate. + * _pkey_:: + * The private key for _certificate_. An instance of OpenSSL::PKey::PKey. + * _extra_certs_:: + * Optional. An array of OpenSSL::X509::Certificate. When sending a + * certificate chain, the certificates specified by this are sent following + * _certificate_, in the order in the array. + * + * === Example + * rsa_cert = OpenSSL::X509::Certificate.new(...) + * rsa_pkey = OpenSSL::PKey.read(...) + * ca_intermediate_cert = OpenSSL::X509::Certificate.new(...) + * ctx.add_certificate(rsa_cert, rsa_pkey, [ca_intermediate_cert]) + * + * ecdsa_cert = ... + * ecdsa_pkey = ... + * another_ca_cert = ... + * ctx.add_certificate(ecdsa_cert, ecdsa_pkey, [another_ca_cert]) + * + * === Note + * OpenSSL before the version 1.0.2 could handle only one extra chain across + * all key types. Calling this method discards the chain set previously. + */ +static VALUE +ossl_sslctx_add_certificate(int argc, VALUE *argv, VALUE self) +{ + VALUE cert, key, extra_chain_ary; + SSL_CTX *ctx; + X509 *x509; + STACK_OF(X509) *extra_chain = NULL; + EVP_PKEY *pkey, *pub_pkey; + + GetSSLCTX(self, ctx); + rb_scan_args(argc, argv, "21", &cert, &key, &extra_chain_ary); + rb_check_frozen(self); + x509 = GetX509CertPtr(cert); + pkey = GetPrivPKeyPtr(key); + + /* + * The reference counter is bumped, and decremented immediately. + * X509_get0_pubkey() is only available in OpenSSL >= 1.1.0. + */ + pub_pkey = X509_get_pubkey(x509); + EVP_PKEY_free(pub_pkey); + if (!pub_pkey) + rb_raise(rb_eArgError, "certificate does not contain public key"); + if (EVP_PKEY_cmp(pub_pkey, pkey) != 1) + rb_raise(rb_eArgError, "public key mismatch"); + + if (argc >= 3) + extra_chain = ossl_x509_ary2sk(extra_chain_ary); + + if (!SSL_CTX_use_certificate(ctx, x509)) { + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_certificate"); + } + if (!SSL_CTX_use_PrivateKey(ctx, pkey)) { + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_use_PrivateKey"); + } + + if (extra_chain) { +#if OPENSSL_VERSION_NUMBER >= 0x10002000 && !defined(LIBRESSL_VERSION_NUMBER) + if (!SSL_CTX_set0_chain(ctx, extra_chain)) { + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_set0_chain"); + } +#else + STACK_OF(X509) *orig_extra_chain; + X509 *x509_tmp; + + /* First, clear the existing chain */ + SSL_CTX_get_extra_chain_certs(ctx, &orig_extra_chain); + if (orig_extra_chain && sk_X509_num(orig_extra_chain)) { + rb_warning("SSL_CTX_set0_chain() is not available; " \ + "clearing previously set certificate chain"); + SSL_CTX_clear_extra_chain_certs(ctx); + } + while ((x509_tmp = sk_X509_shift(extra_chain))) { + /* Transfers ownership */ + if (!SSL_CTX_add_extra_chain_cert(ctx, x509_tmp)) { + X509_free(x509_tmp); + sk_X509_pop_free(extra_chain, X509_free); + ossl_raise(eSSLError, "SSL_CTX_add_extra_chain_cert"); + } + } + sk_X509_free(extra_chain); +#endif + } + return self; +} + /* * call-seq: * ctx.session_add(session) -> true | false @@ -1677,22 +1821,26 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) } ilen = NUM2INT(len); - if(NIL_P(str)) str = rb_str_new(0, ilen); - else{ - StringValue(str); - rb_str_modify(str); - rb_str_resize(str, ilen); + if (NIL_P(str)) + str = rb_str_new(0, ilen); + else { + StringValue(str); + if (RSTRING_LEN(str) >= ilen) + rb_str_modify(str); + else + rb_str_modify_expand(str, ilen - RSTRING_LEN(str)); } - if(ilen == 0) return str; + OBJ_TAINT(str); + rb_str_set_len(str, 0); + if (ilen == 0) + return str; GetSSL(self, ssl); io = rb_attr_get(self, id_i_io); GetOpenFile(io, fptr); if (ssl_started(ssl)) { - if(!nonblock && SSL_pending(ssl) <= 0) - rb_thread_wait_fd(fptr->fd); for (;;){ - nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LENINT(str)); + nread = SSL_read(ssl, RSTRING_PTR(str), ilen); switch(ssl_get_error(ssl, nread)){ case SSL_ERROR_NONE: goto end; @@ -1742,8 +1890,6 @@ ossl_ssl_read_internal(int argc, VALUE *argv, VALUE self, int nonblock) end: rb_str_set_len(str, nread); - OBJ_TAINT(str); - return str; } @@ -2159,7 +2305,7 @@ ossl_ssl_get_client_ca_list(VALUE self) return ossl_x509name_sk2ary(ca); } -# ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB +# ifndef OPENSSL_NO_NEXTPROTONEG /* * call-seq: * ssl.npn_protocol => String | nil @@ -2235,9 +2381,6 @@ ossl_ssl_tmp_key(VALUE self) void Init_ossl_ssl(void) { - int i; - VALUE ary; - #if 0 mOSSL = rb_define_module("OpenSSL"); eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); @@ -2245,6 +2388,7 @@ Init_ossl_ssl(void) rb_mWaitWritable = rb_define_module_under(rb_cIO, "WaitWritable"); #endif + id_call = rb_intern("call"); ID_callback_state = rb_intern("callback_state"); ossl_ssl_ex_vcb_idx = SSL_get_ex_new_index(0, (void *)"ossl_ssl_ex_vcb_idx", 0, 0, 0); @@ -2308,11 +2452,17 @@ Init_ossl_ssl(void) /* * Context certificate + * + * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. + * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern("cert"), 1, 1, Qfalse); /* * Context private key + * + * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. + * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern("key"), 1, 1, Qfalse); @@ -2386,6 +2536,9 @@ Init_ossl_ssl(void) /* * An Array of extra X509 certificates to be added to the certificate * chain. + * + * The _cert_, _key_, and _extra_chain_cert_ attributes are deprecated. + * It is recommended to use #add_certificate instead. */ rb_attr(cSSLContext, rb_intern("extra_chain_cert"), 1, 1, Qfalse); @@ -2441,6 +2594,10 @@ Init_ossl_ssl(void) * A callback invoked when a session is removed from the internal cache. * * The callback is invoked with an SSLContext and a Session. + * + * IMPORTANT NOTE: It is currently not possible to use this safely in a + * multi-threaded application. The callback is called inside a global lock + * and it can randomly cause deadlock on Ruby thread switching. */ rb_attr(cSSLContext, rb_intern("session_remove_cb"), 1, 1, Qfalse); @@ -2468,7 +2625,7 @@ Init_ossl_ssl(void) * end */ rb_attr(cSSLContext, rb_intern("renegotiation_cb"), 1, 1, Qfalse); -#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB +#ifndef OPENSSL_NO_NEXTPROTONEG /* * An Enumerable of Strings. Each String represents a protocol to be * advertised as the list of supported protocols for Next Protocol @@ -2534,12 +2691,17 @@ Init_ossl_ssl(void) rb_define_alias(cSSLContext, "ssl_timeout", "timeout"); rb_define_alias(cSSLContext, "ssl_timeout=", "timeout="); - rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1); + rb_define_private_method(cSSLContext, "set_minmax_proto_version", + ossl_sslctx_set_minmax_proto_version, 2); rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1); rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0); rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1); +#ifdef SSL_MODE_SEND_FALLBACK_SCSV + rb_define_method(cSSLContext, "enable_fallback_scsv", ossl_sslctx_enable_fallback_scsv, 0); +#endif + rb_define_method(cSSLContext, "add_certificate", ossl_sslctx_add_certificate, -1); rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0); rb_define_alias(cSSLContext, "freeze", "setup"); @@ -2602,14 +2764,6 @@ Init_ossl_ssl(void) rb_define_method(cSSLContext, "options", ossl_sslctx_get_options, 0); rb_define_method(cSSLContext, "options=", ossl_sslctx_set_options, 1); - ary = rb_ary_new2(numberof(ossl_ssl_method_tab)); - for (i = 0; i < numberof(ossl_ssl_method_tab); i++) { - rb_ary_push(ary, ID2SYM(rb_intern(ossl_ssl_method_tab[i].name))); - } - rb_obj_freeze(ary); - /* The list of available SSL/TLS methods */ - rb_define_const(cSSLContext, "METHODS", ary); - /* * Document-class: OpenSSL::SSL::SSLSocket */ @@ -2651,49 +2805,112 @@ Init_ossl_ssl(void) # ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB rb_define_method(cSSLSocket, "alpn_protocol", ossl_ssl_alpn_protocol, 0); # endif -# ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB +# ifndef OPENSSL_NO_NEXTPROTONEG rb_define_method(cSSLSocket, "npn_protocol", ossl_ssl_npn_protocol, 0); # endif #endif -#define ossl_ssl_def_const(x) rb_define_const(mSSL, #x, LONG2NUM(SSL_##x)) + rb_define_const(mSSL, "VERIFY_NONE", INT2NUM(SSL_VERIFY_NONE)); + rb_define_const(mSSL, "VERIFY_PEER", INT2NUM(SSL_VERIFY_PEER)); + rb_define_const(mSSL, "VERIFY_FAIL_IF_NO_PEER_CERT", INT2NUM(SSL_VERIFY_FAIL_IF_NO_PEER_CERT)); + rb_define_const(mSSL, "VERIFY_CLIENT_ONCE", INT2NUM(SSL_VERIFY_CLIENT_ONCE)); - ossl_ssl_def_const(VERIFY_NONE); - ossl_ssl_def_const(VERIFY_PEER); - ossl_ssl_def_const(VERIFY_FAIL_IF_NO_PEER_CERT); - ossl_ssl_def_const(VERIFY_CLIENT_ONCE); - /* Introduce constants included in OP_ALL. These constants are mostly for - * unset some bits in OP_ALL such as; - * ctx.options = OP_ALL & ~OP_DONT_INSERT_EMPTY_FRAGMENTS + rb_define_const(mSSL, "OP_ALL", ULONG2NUM(SSL_OP_ALL)); + rb_define_const(mSSL, "OP_LEGACY_SERVER_CONNECT", ULONG2NUM(SSL_OP_LEGACY_SERVER_CONNECT)); +#ifdef SSL_OP_TLSEXT_PADDING /* OpenSSL 1.0.1h and OpenSSL 1.0.2 */ + rb_define_const(mSSL, "OP_TLSEXT_PADDING", ULONG2NUM(SSL_OP_TLSEXT_PADDING)); +#endif +#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG /* OpenSSL 1.0.1f and OpenSSL 1.0.2 */ + rb_define_const(mSSL, "OP_SAFARI_ECDHE_ECDSA_BUG", ULONG2NUM(SSL_OP_SAFARI_ECDHE_ECDSA_BUG)); +#endif +#ifdef SSL_OP_ALLOW_NO_DHE_KEX /* OpenSSL 1.1.1 */ + rb_define_const(mSSL, "OP_ALLOW_NO_DHE_KEX", ULONG2NUM(SSL_OP_ALLOW_NO_DHE_KEX)); +#endif + rb_define_const(mSSL, "OP_DONT_INSERT_EMPTY_FRAGMENTS", ULONG2NUM(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)); + rb_define_const(mSSL, "OP_NO_TICKET", ULONG2NUM(SSL_OP_NO_TICKET)); + rb_define_const(mSSL, "OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION", ULONG2NUM(SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)); + rb_define_const(mSSL, "OP_NO_COMPRESSION", ULONG2NUM(SSL_OP_NO_COMPRESSION)); + rb_define_const(mSSL, "OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION", ULONG2NUM(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); +#ifdef SSL_OP_NO_ENCRYPT_THEN_MAC /* OpenSSL 1.1.1 */ + rb_define_const(mSSL, "OP_NO_ENCRYPT_THEN_MAC", ULONG2NUM(SSL_OP_NO_ENCRYPT_THEN_MAC)); +#endif + rb_define_const(mSSL, "OP_CIPHER_SERVER_PREFERENCE", ULONG2NUM(SSL_OP_CIPHER_SERVER_PREFERENCE)); + rb_define_const(mSSL, "OP_TLS_ROLLBACK_BUG", ULONG2NUM(SSL_OP_TLS_ROLLBACK_BUG)); +#ifdef SSL_OP_NO_RENEGOTIATION /* OpenSSL 1.1.1 */ + rb_define_const(mSSL, "OP_NO_RENEGOTIATION", ULONG2NUM(SSL_OP_NO_RENEGOTIATION)); +#endif + rb_define_const(mSSL, "OP_CRYPTOPRO_TLSEXT_BUG", ULONG2NUM(SSL_OP_CRYPTOPRO_TLSEXT_BUG)); + + rb_define_const(mSSL, "OP_NO_SSLv3", ULONG2NUM(SSL_OP_NO_SSLv3)); + rb_define_const(mSSL, "OP_NO_TLSv1", ULONG2NUM(SSL_OP_NO_TLSv1)); + rb_define_const(mSSL, "OP_NO_TLSv1_1", ULONG2NUM(SSL_OP_NO_TLSv1_1)); + rb_define_const(mSSL, "OP_NO_TLSv1_2", ULONG2NUM(SSL_OP_NO_TLSv1_2)); +#ifdef SSL_OP_NO_TLSv1_3 /* OpenSSL 1.1.1 */ + rb_define_const(mSSL, "OP_NO_TLSv1_3", ULONG2NUM(SSL_OP_NO_TLSv1_3)); +#endif + + /* SSL_OP_* flags for DTLS */ +#if 0 + rb_define_const(mSSL, "OP_NO_QUERY_MTU", ULONG2NUM(SSL_OP_NO_QUERY_MTU)); + rb_define_const(mSSL, "OP_COOKIE_EXCHANGE", ULONG2NUM(SSL_OP_COOKIE_EXCHANGE)); + rb_define_const(mSSL, "OP_CISCO_ANYCONNECT", ULONG2NUM(SSL_OP_CISCO_ANYCONNECT)); +#endif + + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_MICROSOFT_SESS_ID_BUG", ULONG2NUM(SSL_OP_MICROSOFT_SESS_ID_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_NETSCAPE_CHALLENGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_CHALLENGE_BUG)); + /* Deprecated in OpenSSL 0.9.8q and 1.0.0c. */ + rb_define_const(mSSL, "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG)); + /* Deprecated in OpenSSL 1.0.1h and 1.0.2. */ + rb_define_const(mSSL, "OP_SSLREF2_REUSE_CERT_TYPE_BUG", ULONG2NUM(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_MICROSOFT_BIG_SSLV3_BUFFER", ULONG2NUM(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)); + /* Deprecated in OpenSSL 0.9.7h and 0.9.8b. */ + rb_define_const(mSSL, "OP_MSIE_SSLV2_RSA_PADDING", ULONG2NUM(SSL_OP_MSIE_SSLV2_RSA_PADDING)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_SSLEAY_080_CLIENT_DH_BUG", ULONG2NUM(SSL_OP_SSLEAY_080_CLIENT_DH_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_TLS_D5_BUG", ULONG2NUM(SSL_OP_TLS_D5_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_TLS_BLOCK_PADDING_BUG", ULONG2NUM(SSL_OP_TLS_BLOCK_PADDING_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_SINGLE_ECDH_USE", ULONG2NUM(SSL_OP_SINGLE_ECDH_USE)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_SINGLE_DH_USE", ULONG2NUM(SSL_OP_SINGLE_DH_USE)); + /* Deprecated in OpenSSL 1.0.1k and 1.0.2. */ + rb_define_const(mSSL, "OP_EPHEMERAL_RSA", ULONG2NUM(SSL_OP_EPHEMERAL_RSA)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_NO_SSLv2", ULONG2NUM(SSL_OP_NO_SSLv2)); + /* Deprecated in OpenSSL 1.0.1. */ + rb_define_const(mSSL, "OP_PKCS1_CHECK_1", ULONG2NUM(SSL_OP_PKCS1_CHECK_1)); + /* Deprecated in OpenSSL 1.0.1. */ + rb_define_const(mSSL, "OP_PKCS1_CHECK_2", ULONG2NUM(SSL_OP_PKCS1_CHECK_2)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_NETSCAPE_CA_DN_BUG", ULONG2NUM(SSL_OP_NETSCAPE_CA_DN_BUG)); + /* Deprecated in OpenSSL 1.1.0. */ + rb_define_const(mSSL, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", ULONG2NUM(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG)); + + + /* + * SSL/TLS version constants. Used by SSLContext#min_version= and + * #max_version= */ - ossl_ssl_def_const(OP_MICROSOFT_SESS_ID_BUG); - ossl_ssl_def_const(OP_NETSCAPE_CHALLENGE_BUG); - ossl_ssl_def_const(OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG); - ossl_ssl_def_const(OP_SSLREF2_REUSE_CERT_TYPE_BUG); - ossl_ssl_def_const(OP_MICROSOFT_BIG_SSLV3_BUFFER); - ossl_ssl_def_const(OP_MSIE_SSLV2_RSA_PADDING); - ossl_ssl_def_const(OP_SSLEAY_080_CLIENT_DH_BUG); - ossl_ssl_def_const(OP_TLS_D5_BUG); - ossl_ssl_def_const(OP_TLS_BLOCK_PADDING_BUG); - ossl_ssl_def_const(OP_DONT_INSERT_EMPTY_FRAGMENTS); - ossl_ssl_def_const(OP_ALL); - ossl_ssl_def_const(OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - ossl_ssl_def_const(OP_SINGLE_ECDH_USE); - ossl_ssl_def_const(OP_SINGLE_DH_USE); - ossl_ssl_def_const(OP_EPHEMERAL_RSA); - ossl_ssl_def_const(OP_CIPHER_SERVER_PREFERENCE); - ossl_ssl_def_const(OP_TLS_ROLLBACK_BUG); - ossl_ssl_def_const(OP_NO_SSLv2); - ossl_ssl_def_const(OP_NO_SSLv3); - ossl_ssl_def_const(OP_NO_TLSv1); - ossl_ssl_def_const(OP_NO_TLSv1_1); - ossl_ssl_def_const(OP_NO_TLSv1_2); - ossl_ssl_def_const(OP_NO_TICKET); - ossl_ssl_def_const(OP_NO_COMPRESSION); - ossl_ssl_def_const(OP_PKCS1_CHECK_1); - ossl_ssl_def_const(OP_PKCS1_CHECK_2); - ossl_ssl_def_const(OP_NETSCAPE_CA_DN_BUG); - ossl_ssl_def_const(OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); + /* SSL 2.0 */ + rb_define_const(mSSL, "SSL2_VERSION", INT2NUM(SSL2_VERSION)); + /* SSL 3.0 */ + rb_define_const(mSSL, "SSL3_VERSION", INT2NUM(SSL3_VERSION)); + /* TLS 1.0 */ + rb_define_const(mSSL, "TLS1_VERSION", INT2NUM(TLS1_VERSION)); + /* TLS 1.1 */ + rb_define_const(mSSL, "TLS1_1_VERSION", INT2NUM(TLS1_1_VERSION)); + /* TLS 1.2 */ + rb_define_const(mSSL, "TLS1_2_VERSION", INT2NUM(TLS1_2_VERSION)); +#ifdef TLS1_3_VERSION /* OpenSSL 1.1.1 */ + /* TLS 1.3 */ + rb_define_const(mSSL, "TLS1_3_VERSION", INT2NUM(TLS1_3_VERSION)); +#endif + sym_exception = ID2SYM(rb_intern("exception")); sym_wait_readable = ID2SYM(rb_intern("wait_readable")); diff --git a/ext/openssl/ossl_x509cert.c b/ext/openssl/ossl_x509cert.c index 003a9c19..40542c4a 100644 --- a/ext/openssl/ossl_x509cert.c +++ b/ext/openssl/ossl_x509cert.c @@ -440,7 +440,7 @@ ossl_x509_set_not_before(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); - if (!X509_set_notBefore(x509, asn1time)) { + if (!X509_set1_notBefore(x509, asn1time)) { ASN1_TIME_free(asn1time); ossl_raise(eX509CertError, "X509_set_notBefore"); } @@ -479,7 +479,7 @@ ossl_x509_set_not_after(VALUE self, VALUE time) GetX509(self, x509); asn1time = ossl_x509_time_adjust(NULL, time); - if (!X509_set_notAfter(x509, asn1time)) { + if (!X509_set1_notAfter(x509, asn1time)) { ASN1_TIME_free(asn1time); ossl_raise(eX509CertError, "X509_set_notAfter"); } @@ -508,18 +508,19 @@ ossl_x509_get_public_key(VALUE self) /* * call-seq: - * cert.public_key = key => key + * cert.public_key = key */ static VALUE ossl_x509_set_public_key(VALUE self, VALUE key) { X509 *x509; + EVP_PKEY *pkey; GetX509(self, x509); - if (!X509_set_pubkey(x509, GetPKeyPtr(key))) { /* DUPs pkey */ - ossl_raise(eX509CertError, NULL); - } - + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); + if (!X509_set_pubkey(x509, pkey)) + ossl_raise(eX509CertError, "X509_set_pubkey"); return key; } @@ -557,9 +558,9 @@ ossl_x509_verify(VALUE self, VALUE key) X509 *x509; EVP_PKEY *pkey; - pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ GetX509(self, x509); - + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); switch (X509_verify(x509, pkey)) { case 1: return Qtrue; @@ -684,6 +685,26 @@ ossl_x509_inspect(VALUE self) } /* + * call-seq: + * cert1 == cert2 -> true | false + * + * Compares the two certificates. Note that this takes into account all fields, + * not just the issuer name and the serial number. + */ +static VALUE +ossl_x509_eq(VALUE self, VALUE other) +{ + X509 *a, *b; + + GetX509(self, a); + if (!rb_obj_is_kind_of(other, cX509Cert)) + return Qfalse; + GetX509(other, b); + + return !X509_cmp(a, b) ? Qtrue : Qfalse; +} + +/* * INIT */ void @@ -821,4 +842,5 @@ Init_ossl_x509cert(void) rb_define_method(cX509Cert, "extensions=", ossl_x509_set_extensions, 1); rb_define_method(cX509Cert, "add_extension", ossl_x509_add_extension, 1); rb_define_method(cX509Cert, "inspect", ossl_x509_inspect, 0); + rb_define_method(cX509Cert, "==", ossl_x509_eq, 1); } diff --git a/ext/openssl/ossl_x509crl.c b/ext/openssl/ossl_x509crl.c index 5ecd7ea0..b0badf45 100644 --- a/ext/openssl/ossl_x509crl.c +++ b/ext/openssl/ossl_x509crl.c @@ -226,7 +226,7 @@ ossl_x509crl_set_last_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); - if (!X509_CRL_set_lastUpdate(crl, asn1time)) { + if (!X509_CRL_set1_lastUpdate(crl, asn1time)) { ASN1_TIME_free(asn1time); ossl_raise(eX509CRLError, "X509_CRL_set_lastUpdate"); } @@ -257,7 +257,7 @@ ossl_x509crl_set_next_update(VALUE self, VALUE time) GetX509CRL(self, crl); asn1time = ossl_x509_time_adjust(NULL, time); - if (!X509_CRL_set_nextUpdate(crl, asn1time)) { + if (!X509_CRL_set1_nextUpdate(crl, asn1time)) { ASN1_TIME_free(asn1time); ossl_raise(eX509CRLError, "X509_CRL_set_nextUpdate"); } @@ -359,9 +359,12 @@ static VALUE ossl_x509crl_verify(VALUE self, VALUE key) { X509_CRL *crl; + EVP_PKEY *pkey; GetX509CRL(self, crl); - switch (X509_CRL_verify(crl, GetPKeyPtr(key))) { + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); + switch (X509_CRL_verify(crl, pkey)) { case 1: return Qtrue; case 0: diff --git a/ext/openssl/ossl_x509name.c b/ext/openssl/ossl_x509name.c index ccb2541f..c900bcbe 100644 --- a/ext/openssl/ossl_x509name.c +++ b/ext/openssl/ossl_x509name.c @@ -260,6 +260,23 @@ ossl_x509name_to_s_old(VALUE self) return str; } +static VALUE +x509name_print(VALUE self, unsigned long iflag) +{ + X509_NAME *name; + BIO *out; + + GetX509Name(self, name); + out = BIO_new(BIO_s_mem()); + if (!out) + ossl_raise(eX509NameError, NULL); + if (!X509_NAME_print_ex(out, name, 0, iflag)) { + BIO_free(out); + ossl_raise(eX509NameError, "X509_NAME_print_ex"); + } + return ossl_membio2str(out); +} + /* * call-seq: * name.to_s -> string @@ -279,27 +296,37 @@ ossl_x509name_to_s_old(VALUE self) static VALUE ossl_x509name_to_s(int argc, VALUE *argv, VALUE self) { - X509_NAME *name; - VALUE flag, str; - BIO *out; - unsigned long iflag; - - rb_scan_args(argc, argv, "01", &flag); - if (NIL_P(flag)) + rb_check_arity(argc, 0, 1); + /* name.to_s(nil) was allowed */ + if (!argc || NIL_P(argv[0])) return ossl_x509name_to_s_old(self); - else iflag = NUM2ULONG(flag); - if (!(out = BIO_new(BIO_s_mem()))) - ossl_raise(eX509NameError, NULL); - GetX509Name(self, name); - if (!X509_NAME_print_ex(out, name, 0, iflag)){ - BIO_free(out); - ossl_raise(eX509NameError, NULL); - } - str = ossl_membio2str(out); + else + return x509name_print(self, NUM2ULONG(argv[0])); +} +/* + * call-seq: + * name.to_utf8 -> string + * + * Returns an UTF-8 representation of the distinguished name, as specified + * in {RFC 2253}[https://www.ietf.org/rfc/rfc2253.txt]. + */ +static VALUE +ossl_x509name_to_utf8(VALUE self) +{ + VALUE str = x509name_print(self, XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB); + rb_enc_associate_index(str, rb_utf8_encindex()); return str; } +/* :nodoc: */ +static VALUE +ossl_x509name_inspect(VALUE self) +{ + return rb_enc_sprintf(rb_utf8_encoding(), "#<%"PRIsVALUE" %"PRIsVALUE">", + rb_obj_class(self), ossl_x509name_to_utf8(self)); +} + /* * call-seq: * name.to_a => [[name, data, type], ...] @@ -494,6 +521,8 @@ Init_ossl_x509name(void) rb_define_method(cX509Name, "initialize_copy", ossl_x509name_initialize_copy, 1); rb_define_method(cX509Name, "add_entry", ossl_x509name_add_entry, -1); rb_define_method(cX509Name, "to_s", ossl_x509name_to_s, -1); + rb_define_method(cX509Name, "to_utf8", ossl_x509name_to_utf8, 0); + rb_define_method(cX509Name, "inspect", ossl_x509name_inspect, 0); rb_define_method(cX509Name, "to_a", ossl_x509name_to_a, 0); rb_define_method(cX509Name, "cmp", ossl_x509name_cmp, 1); rb_define_alias(cX509Name, "<=>", "cmp"); diff --git a/ext/openssl/ossl_x509req.c b/ext/openssl/ossl_x509req.c index 9f20dba3..2c20042a 100644 --- a/ext/openssl/ossl_x509req.c +++ b/ext/openssl/ossl_x509req.c @@ -293,11 +293,10 @@ ossl_x509req_set_public_key(VALUE self, VALUE key) EVP_PKEY *pkey; GetX509Req(self, req); - pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ - if (!X509_REQ_set_pubkey(req, pkey)) { - ossl_raise(eX509ReqError, NULL); - } - + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); + if (!X509_REQ_set_pubkey(req, pkey)) + ossl_raise(eX509ReqError, "X509_REQ_set_pubkey"); return key; } @@ -328,7 +327,8 @@ ossl_x509req_verify(VALUE self, VALUE key) EVP_PKEY *pkey; GetX509Req(self, req); - pkey = GetPKeyPtr(key); /* NO NEED TO DUP */ + pkey = GetPKeyPtr(key); + ossl_pkey_check_public_key(pkey); switch (X509_REQ_verify(req, pkey)) { case 1: return Qtrue; diff --git a/ext/openssl/ossl_x509revoked.c b/ext/openssl/ossl_x509revoked.c index 85489efd..5fe68534 100644 --- a/ext/openssl/ossl_x509revoked.c +++ b/ext/openssl/ossl_x509revoked.c @@ -249,6 +249,26 @@ ossl_x509revoked_add_extension(VALUE self, VALUE ext) return ext; } +static VALUE +ossl_x509revoked_to_der(VALUE self) +{ + X509_REVOKED *rev; + VALUE str; + int len; + unsigned char *p; + + GetX509Rev(self, rev); + len = i2d_X509_REVOKED(rev, NULL); + if (len <= 0) + ossl_raise(eX509RevError, "i2d_X509_REVOKED"); + str = rb_str_new(NULL, len); + p = (unsigned char *)RSTRING_PTR(str); + if (i2d_X509_REVOKED(rev, &p) <= 0) + ossl_raise(eX509RevError, "i2d_X509_REVOKED"); + ossl_str_adjust(str, p); + return str; +} + /* * INIT */ @@ -276,4 +296,5 @@ Init_ossl_x509revoked(void) rb_define_method(cX509Rev, "extensions", ossl_x509revoked_get_extensions, 0); rb_define_method(cX509Rev, "extensions=", ossl_x509revoked_set_extensions, 1); rb_define_method(cX509Rev, "add_extension", ossl_x509revoked_add_extension, 1); + rb_define_method(cX509Rev, "to_der", ossl_x509revoked_to_der, 0); } diff --git a/ext/openssl/ruby_missing.h b/ext/openssl/ruby_missing.h index b8a0a0c1..069acc8b 100644 --- a/ext/openssl/ruby_missing.h +++ b/ext/openssl/ruby_missing.h @@ -10,9 +10,15 @@ #if !defined(_OSSL_RUBY_MISSING_H_) #define _OSSL_RUBY_MISSING_H_ +/* Ruby 2.4 */ #ifndef RB_INTEGER_TYPE_P -/* for Ruby 2.3 compatibility */ -#define RB_INTEGER_TYPE_P(obj) (RB_FIXNUM_P(obj) || RB_TYPE_P(obj, T_BIGNUM)) +# define RB_INTEGER_TYPE_P(obj) (RB_FIXNUM_P(obj) || RB_TYPE_P(obj, T_BIGNUM)) +#endif + +/* Ruby 2.5 */ +#ifndef ST2FIX +# define RB_ST2FIX(h) LONG2FIX((long)(h)) +# define ST2FIX(h) RB_ST2FIX(h) #endif #endif /* _OSSL_RUBY_MISSING_H_ */ diff --git a/lib/openssl/buffering.rb b/lib/openssl/buffering.rb index 8b5dd9da..935f61f0 100644 --- a/lib/openssl/buffering.rb +++ b/lib/openssl/buffering.rb @@ -339,9 +339,11 @@ module OpenSSL::Buffering # Writes _s_ to the stream. If the argument is not a String it will be # converted using +.to_s+ method. Returns the number of bytes written. - def write(s) - do_write(s) - s.bytesize + def write(*s) + s.inject(0) do |written, str| + do_write(str) + written + str.bytesize + end end ## diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index ced46b0d..6a6f2b94 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -17,29 +17,18 @@ module OpenSSL module SSL class SSLContext DEFAULT_PARAMS = { # :nodoc: - :ssl_version => "SSLv23", + :min_version => OpenSSL::SSL::TLS1_VERSION, :verify_mode => OpenSSL::SSL::VERIFY_PEER, :verify_hostname => true, :options => -> { opts = OpenSSL::SSL::OP_ALL opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS opts |= OpenSSL::SSL::OP_NO_COMPRESSION - opts |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 opts }.call } if defined?(OpenSSL::PKey::DH) - # :nodoc: - DEFAULT_1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_ ------BEGIN DH PARAMETERS----- -MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ -AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR -T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC ------END DH PARAMETERS----- - _end_of_pem_ - - # :nodoc: DEFAULT_2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ -----BEGIN DH PARAMETERS----- MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY @@ -50,15 +39,11 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== -----END DH PARAMETERS----- _end_of_pem_ + private_constant :DEFAULT_2048 - # :nodoc: - DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| + DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc: warn "using default DH parameters." if $VERBOSE - case keylen - when 1024 then DEFAULT_1024 - when 2048 then DEFAULT_2048 - else nil - end + DEFAULT_2048 } end @@ -123,11 +108,15 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 attr_accessor :servername_cb # call-seq: - # SSLContext.new => ctx - # SSLContext.new(:TLSv1) => ctx - # SSLContext.new("SSLv23_client") => ctx + # SSLContext.new -> ctx + # SSLContext.new(:TLSv1) -> ctx + # SSLContext.new("SSLv23") -> ctx + # + # Creates a new SSL context. # - # You can get a list of valid methods with OpenSSL::SSL::SSLContext::METHODS + # If an argument is given, #ssl_version= is called with the value. Note + # that this form is deprecated. New applications should use #min_version= + # and #max_version= as necessary. def initialize(version = nil) self.options |= OpenSSL::SSL::OP_ALL self.ssl_version = version if version @@ -147,6 +136,7 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 # used. def set_params(params={}) params = DEFAULT_PARAMS.merge(params) + self.options = params.delete(:options) # set before min_version/max_version params.each{|name, value| self.__send__("#{name}=", value) } if self.verify_mode != OpenSSL::SSL::VERIFY_NONE unless self.ca_file or self.ca_path or self.cert_store @@ -155,6 +145,88 @@ YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 end return params end + + # call-seq: + # ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION + # ctx.min_version = :TLS1_2 + # ctx.min_version = nil + # + # Sets the lower bound on the supported SSL/TLS protocol version. The + # version may be specified by an integer constant named + # OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version". + # + # Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v* + # options by #options= once you have called #min_version= or + # #max_version=. + # + # === Example + # ctx = OpenSSL::SSL::SSLContext.new + # ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION + # ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + # + # sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx) + # sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2 + def min_version=(version) + set_minmax_proto_version(version, @max_proto_version ||= nil) + @min_proto_version = version + end + + # call-seq: + # ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + # ctx.max_version = :TLS1_2 + # ctx.max_version = nil + # + # Sets the upper bound of the supported SSL/TLS protocol version. See + # #min_version= for the possible values. + def max_version=(version) + set_minmax_proto_version(@min_proto_version ||= nil, version) + @max_proto_version = version + end + + # call-seq: + # ctx.ssl_version = :TLSv1 + # ctx.ssl_version = "SSLv23" + # + # Sets the SSL/TLS protocol version for the context. This forces + # connections to use only the specified protocol version. This is + # deprecated and only provided for backwards compatibility. Use + # #min_version= and #max_version= instead. + # + # === History + # As the name hints, this used to call the SSL_CTX_set_ssl_version() + # function which sets the SSL method used for connections created from + # the context. As of Ruby/OpenSSL 2.1, this accessor method is + # implemented to call #min_version= and #max_version= instead. + def ssl_version=(meth) + meth = meth.to_s if meth.is_a?(Symbol) + if /(?<type>_client|_server)\z/ =~ meth + meth = $` + if $VERBOSE + warn "#{caller(1, 1)[0]}: method type #{type.inspect} is ignored" + end + end + version = METHODS_MAP[meth.intern] or + raise ArgumentError, "unknown SSL method `%s'" % meth + set_minmax_proto_version(version, version) + @min_proto_version = @max_proto_version = version + end + + METHODS_MAP = { + SSLv23: 0, + SSLv2: OpenSSL::SSL::SSL2_VERSION, + SSLv3: OpenSSL::SSL::SSL3_VERSION, + TLSv1: OpenSSL::SSL::TLS1_VERSION, + TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION, + TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION, + }.freeze + private_constant :METHODS_MAP + + # The list of available SSL/TLS methods. This constant is only provided + # for backwards compatibility. + METHODS = METHODS_MAP.flat_map { |name,| + [name, :"#{name}_client", :"#{name}_server"] + }.freeze + deprecate_constant :METHODS end module SocketForwarder diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb index aef3456e..98358f90 100644 --- a/lib/openssl/x509.rb +++ b/lib/openssl/x509.rb @@ -41,6 +41,11 @@ module OpenSSL end class Extension + def ==(other) + return false unless Extension === other + to_der == other.to_der + end + def to_s # "oid = critical, value" str = self.oid str << " = " @@ -139,7 +144,13 @@ module OpenSSL end def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE) - ary = str.scan(/\s*([^\/,]+)\s*/).collect{|i| i[0].split("=", 2) } + if str.start_with?("/") + # /A=B/C=D format + ary = str[1..-1].split("/").map { |i| i.split("=", 2) } + else + # Comma-separated + ary = str.split(",").map { |i| i.strip.split("=", 2) } + end self.new(ary, template) end @@ -154,6 +165,13 @@ module OpenSSL end end + class Attribute + def ==(other) + return false unless Attribute === other + to_der == other.to_der + end + end + class StoreContext def cleanup warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE @@ -172,5 +190,26 @@ module OpenSSL } end end + + class CRL + def ==(other) + return false unless CRL === other + to_der == other.to_der + end + end + + class Revoked + def ==(other) + return false unless Revoked === other + to_der == other.to_der + end + end + + class Request + def ==(other) + return false unless Request === other + to_der == other.to_der + end + end end end diff --git a/openssl.gemspec b/openssl.gemspec index 470837dd..e2968efd 100644 --- a/openssl.gemspec +++ b/openssl.gemspec @@ -1,11 +1,11 @@ Gem::Specification.new do |spec| spec.name = "openssl" - spec.version = "2.1.0" + spec.version = "2.1.0.beta2" spec.authors = ["Martin Bosslet", "SHIBATA Hiroshi", "Zachary Scott", "Kazuki Yamaguchi"] spec.email = ["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://www.ruby-lang.org/" + spec.homepage = "https://github.com/ruby/openssl" spec.license = "Ruby" spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "LICENSE.txt"] diff --git a/test/envutil.rb b/test/envutil.rb index 89332b35..05d6fe27 100644 --- a/test/envutil.rb +++ b/test/envutil.rb @@ -92,6 +92,18 @@ module EnvUtil end module_function :invoke_ruby + def verbose_warning + class << (stderr = "".dup) + alias write << + end + stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true + yield stderr + return $stderr + ensure + stderr, $stderr, $VERBOSE = $stderr, stderr, verbose + end + module_function :verbose_warning + def suppress_warning verbose, $VERBOSE = $VERBOSE, nil yield @@ -220,6 +232,17 @@ eom raise marshal_error if marshal_error end + def assert_warning(pat, msg = nil) + stderr = EnvUtil.verbose_warning { + yield + } + if Regexp === pat + assert_match pat, stderr, msg + else + assert_equal pat, stderr, msg + end + end + def message msg = nil, ending = ".", &default proc { msg = msg.call.chomp(".") if Proc === msg diff --git a/test/test_asn1.rb b/test/test_asn1.rb index 9ac6b9be..11707037 100644 --- a/test/test_asn1.rb +++ b/test/test_asn1.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestASN1 < OpenSSL::TestCase def test_decode_x509_certificate subj = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=TestCA") @@ -377,11 +379,16 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase def test_utctime encode_decode_test B(%w{ 17 0D }) + "160908234339Z".b, OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 39)) - # possible range of UTCTime is 1969-2068 currently - encode_decode_test B(%w{ 17 0D }) + "690908234339Z".b, - OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 39)) - decode_test B(%w{ 17 0B }) + "6909082343Z".b, - OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 0)) + # Seconds is omitted + decode_test B(%w{ 17 0B }) + "1609082343Z".b, + OpenSSL::ASN1::UTCTime.new(Time.utc(2016, 9, 8, 23, 43, 0)) + begin + # possible range of UTCTime is 1969-2068 currently + encode_decode_test B(%w{ 17 0D }) + "690908234339Z".b, + OpenSSL::ASN1::UTCTime.new(Time.utc(1969, 9, 8, 23, 43, 39)) + rescue OpenSSL::ASN1::ASN1Error + pend "No negative time_t support?" + end # not implemented # decode_test B(%w{ 17 11 }) + "500908234339+0930".b, # OpenSSL::ASN1::UTCTime.new(Time.new(1950, 9, 8, 23, 43, 39, "+09:30")) @@ -677,5 +684,6 @@ class OpenSSL::TestASN1 < OpenSSL::TestCase end assert_equal(:UNIVERSAL, asn1.tag_class) end +end end diff --git a/test/test_bn.rb b/test/test_bn.rb index 02c53f39..274afba3 100644 --- a/test/test_bn.rb +++ b/test/test_bn.rb @@ -3,6 +3,8 @@ require_relative 'utils' require "prime" +if defined?(OpenSSL) + class OpenSSL::TestBN < OpenSSL::TestCase def setup super @@ -268,5 +270,8 @@ class OpenSSL::TestBN < OpenSSL::TestCase assert_equal(1, @e1.cmp(-999)) assert_equal(0, @e1.ucmp(999)) assert_equal(0, @e1.ucmp(-999)) + assert_instance_of(String, @e1.hash.to_s) end end + +end diff --git a/test/test_buffering.rb b/test/test_buffering.rb index 73cfa9d8..c85a6f02 100644 --- a/test/test_buffering.rb +++ b/test/test_buffering.rb @@ -1,9 +1,9 @@ # frozen_string_literal: false require_relative 'utils' -require 'stringio' -class OpenSSL::TestBuffering < OpenSSL::TestCase +if defined?(OpenSSL) +class OpenSSL::TestBuffering < OpenSSL::TestCase class IO include OpenSSL::Buffering @@ -85,5 +85,6 @@ class OpenSSL::TestBuffering < OpenSSL::TestCase end assert_equal([97, 98, 99], res) end +end end diff --git a/test/test_cipher.rb b/test/test_cipher.rb index ce64fbb9..56061741 100644 --- a/test/test_cipher.rb +++ b/test/test_cipher.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestCipher < OpenSSL::TestCase module Helper def has_cipher?(name) @@ -293,6 +295,13 @@ class OpenSSL::TestCipher < OpenSSL::TestCase assert_equal tag1, tag2 end + def test_non_aead_cipher_set_auth_data + assert_raise(OpenSSL::Cipher::CipherError) { + cipher = OpenSSL::Cipher.new("aes-128-cfb").encrypt + cipher.auth_data = "123" + } + end + private def new_encryptor(algo, **kwargs) @@ -308,5 +317,6 @@ class OpenSSL::TestCipher < OpenSSL::TestCase kwargs.each {|k, v| cipher.send(:"#{k}=", v) } end end +end end diff --git a/test/test_config.rb b/test/test_config.rb index 42f9d3c5..8096375c 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestConfig < OpenSSL::TestCase def setup super @@ -171,7 +173,7 @@ __EOC__ def test_value # suppress deprecation warnings - OpenSSL::TestUtils.silent do + EnvUtil.suppress_warning do assert_equal('CA_default', @it.value('ca', 'default_ca')) assert_equal(nil, @it.value('ca', 'no such key')) assert_equal(nil, @it.value('no such section', 'no such key')) @@ -184,7 +186,7 @@ __EOC__ end def test_value_ENV - OpenSSL::TestUtils.silent do + EnvUtil.suppress_warning do key = ENV.keys.first assert_not_nil(key) # make sure we have at least one ENV var. assert_equal(ENV[key], @it.value('ENV', key)) @@ -199,7 +201,7 @@ __EOC__ end def test_section - OpenSSL::TestUtils.silent do + EnvUtil.suppress_warning do assert_equal({'HOME' => '.'}, @it.section('default')) assert_equal({'dir' => './demoCA', 'certs' => './certs'}, @it.section('CA_default')) assert_equal({}, @it.section('no_such_section')) @@ -298,3 +300,5 @@ __EOC__ assert_not_equal(@it.sections.sort, c.sections.sort) end end + +end diff --git a/test/test_digest.rb b/test/test_digest.rb index e81d618d..2cb878b6 100644 --- a/test/test_digest.rb +++ b/test/test_digest.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestDigest < OpenSSL::TestCase def setup super @@ -53,7 +55,7 @@ class OpenSSL::TestDigest < OpenSSL::TestCase def test_digest_constants algs = %w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512) - if OpenSSL::OPENSSL_VERSION_NUMBER < 0x10100000 + if !libressl? && !openssl?(1, 1, 0) algs += %w(DSS1 SHA) end algs.each do |alg| @@ -115,3 +117,5 @@ class OpenSSL::TestDigest < OpenSSL::TestCase assert_not_nil(d) end end + +end diff --git a/test/test_engine.rb b/test/test_engine.rb index a987f267..bb1123d5 100644 --- a/test/test_engine.rb +++ b/test/test_engine.rb @@ -1,8 +1,9 @@ # frozen_string_literal: false require_relative 'utils' -class OpenSSL::TestEngine < OpenSSL::TestCase +if defined?(OpenSSL) && defined?(OpenSSL::Engine) +class OpenSSL::TestEngine < OpenSSL::TestCase def test_engines_free # [ruby-dev:44173] with_openssl <<-'end;' OpenSSL::Engine.load("openssl") @@ -51,32 +52,28 @@ class OpenSSL::TestEngine < OpenSSL::TestCase end def test_openssl_engine_cipher_rc4 - with_openssl <<-'end;' - begin - engine = get_engine - algo = "RC4" #AES is not supported by openssl Engine (<=1.0.0e) - data = "a" * 1000 - key = OpenSSL::Random.random_bytes(16) - # suppress message from openssl Engine's RC4 cipher [ruby-core:41026] - err_back = $stderr.dup - $stderr.reopen(IO::NULL) - encrypted = crypt_data(data, key, :encrypt) { engine.cipher(algo) } - decrypted = crypt_data(encrypted, key, :decrypt) { OpenSSL::Cipher.new(algo) } - assert_equal(data, decrypted) - ensure - if err_back - $stderr.reopen(err_back) - err_back.close - end - end + begin + OpenSSL::Cipher.new("rc4") + rescue OpenSSL::Cipher::CipherError + pend "RC4 is not supported" + end + + with_openssl(<<-'end;', ignore_stderr: true) + engine = get_engine + algo = "RC4" + data = "a" * 1000 + key = OpenSSL::Random.random_bytes(16) + encrypted = crypt_data(data, key, :encrypt) { engine.cipher(algo) } + decrypted = crypt_data(encrypted, key, :decrypt) { OpenSSL::Cipher.new(algo) } + assert_equal(data, decrypted) end; end private # this is required because OpenSSL::Engine methods change global state - def with_openssl(code) - assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") + def with_openssl(code, **opts) + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;", **opts) require #{__FILE__.dump} include OpenSSL::TestEngine::Utils #{code} @@ -95,5 +92,6 @@ class OpenSSL::TestEngine < OpenSSL::TestCase cipher.update(data) + cipher.final end end +end -end if defined?(OpenSSL::Engine) +end diff --git a/test/test_fips.rb b/test/test_fips.rb index e96c5c07..a577d789 100644 --- a/test/test_fips.rb +++ b/test/test_fips.rb @@ -1,20 +1,30 @@ # frozen_string_literal: false require_relative 'utils' -class OpenSSL::TestFIPS < OpenSSL::TestCase +if defined?(OpenSSL) +class OpenSSL::TestFIPS < OpenSSL::TestCase def test_fips_mode_is_reentrant OpenSSL.fips_mode = false OpenSSL.fips_mode = false end def test_fips_mode_get - if OpenSSL::OPENSSL_FIPS - OpenSSL.fips_mode = true - assert OpenSSL.fips_mode == true, ".fips_mode returns true when .fips_mode=true" + return unless OpenSSL::OPENSSL_FIPS + assert_separately([{ "OSSL_MDEBUG" => nil }, "-ropenssl"], <<~"end;") + require #{__FILE__.dump} + + begin + OpenSSL.fips_mode = true + assert OpenSSL.fips_mode == true, ".fips_mode returns true when .fips_mode=true" - OpenSSL.fips_mode = false - assert OpenSSL.fips_mode == false, ".fips_mode returns false when .fips_mode=false" - end + OpenSSL.fips_mode = false + assert OpenSSL.fips_mode == false, ".fips_mode returns false when .fips_mode=false" + rescue OpenSSL::OpenSSLError + pend "Could not set FIPS mode (OpenSSL::OpenSSLError: \#$!); skipping" + end + end; end end + +end diff --git a/test/test_hmac.rb b/test/test_hmac.rb index 86857ba0..831a5b6b 100644 --- a/test/test_hmac.rb +++ b/test/test_hmac.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestHMAC < OpenSSL::TestCase def test_hmac # RFC 2202 2. Test Cases for HMAC-MD5 @@ -38,3 +40,5 @@ class OpenSSL::TestHMAC < OpenSSL::TestCase assert_equal first, second end end + +end diff --git a/test/test_kdf.rb b/test/test_kdf.rb index 9346be7c..5e1db80c 100644 --- a/test/test_kdf.rb +++ b/test/test_kdf.rb @@ -1,8 +1,9 @@ # frozen_string_literal: false require_relative 'utils' -class OpenSSL::TestKDF < OpenSSL::TestCase +if defined?(OpenSSL) +class OpenSSL::TestKDF < OpenSSL::TestCase def test_pkcs5_pbkdf2_hmac_compatibility expected = OpenSSL::KDF.pbkdf2_hmac("password", salt: "salt", iterations: 1, length: 20, hash: "sha1") assert_equal(expected, OpenSSL::PKCS5.pbkdf2_hmac("password", "salt", 1, 20, "sha1")) @@ -130,10 +131,53 @@ class OpenSSL::TestKDF < OpenSSL::TestCase assert_equal(expected, OpenSSL::KDF.scrypt(pass, salt: salt, N: n, r: r, p: p, length: dklen)) end + def test_hkdf_rfc5869_test_case_1 + pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 + hash = "sha256" + ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") + salt = B("000102030405060708090a0b0c") + info = B("f0f1f2f3f4f5f6f7f8f9") + l = 42 + + okm = B("3cb25f25faacd57a90434f64d0362f2a" \ + "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" \ + "34007208d5b887185865") + assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) + end + + def test_hkdf_rfc5869_test_case_3 + pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 + hash = "sha256" + ikm = B("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") + salt = B("") + info = B("") + l = 42 + + okm = B("8da4e775a563c18f715f802a063c5a31" \ + "b8a11f5c5ee1879ec3454e5f3c738d2d" \ + "9d201395faa4b61a96c8") + assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) + end + + def test_hkdf_rfc5869_test_case_4 + pend "HKDF is not implemented" unless OpenSSL::KDF.respond_to?(:hkdf) # OpenSSL >= 1.1.0 + hash = "sha1" + ikm = B("0b0b0b0b0b0b0b0b0b0b0b") + salt = B("000102030405060708090a0b0c") + info = B("f0f1f2f3f4f5f6f7f8f9") + l = 42 + + okm = B("085a01ea1b10f36933068b56efa5ad81" \ + "a4f14b822f5b091568a9cdd4f155fda2" \ + "c22e422478d305f3f896") + assert_equal(okm, OpenSSL::KDF.hkdf(ikm, salt: salt, info: info, length: l, hash: hash)) + end + private def B(ary) [Array(ary).join].pack("H*") end +end end diff --git a/test/test_ns_spki.rb b/test/test_ns_spki.rb index 4905fba4..aa1e6182 100644 --- a/test/test_ns_spki.rb +++ b/test/test_ns_spki.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestNSSPI < OpenSSL::TestCase def setup super @@ -47,3 +49,5 @@ class OpenSSL::TestNSSPI < OpenSSL::TestCase assert_equal(OpenSSL::PKey::RSA, spki.public_key.class) end end + +end diff --git a/test/test_ocsp.rb b/test/test_ocsp.rb index 25e052ae..50ad6c31 100644 --- a/test/test_ocsp.rb +++ b/test/test_ocsp.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestOCSP < OpenSSL::TestCase def setup super @@ -120,12 +122,12 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase assert_equal true, req.verify([@cert], store, OpenSSL::OCSP::NOINTERN) ret = req.verify([@cert], store) - if ret || OpenSSL::OPENSSL_VERSION =~ /OpenSSL/ && OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 + if ret || openssl?(1, 0, 2) assert_equal true, ret else # RT2560; OCSP_request_verify() does not find signer cert from 'certs' when # OCSP_NOINTERN is not specified. - # fixed by OpenSSL 1.0.1j, 1.0.2 and LibreSSL 2.4.2 + # fixed by OpenSSL 1.0.1j, 1.0.2 pend "RT2560: ocsp_req_find_signer" end @@ -260,11 +262,6 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED, -400, -300, nil, []) bres.add_status(cid2, OpenSSL::OCSP::V_CERTSTATUS_GOOD, nil, nil, Time.now + 100, nil, nil) - if bres.responses[2].check_validity # thisUpdate is in future; must fail - # LibreSSL bug; skip for now - pend "OCSP_check_validity() is broken" - end - single1 = bres.responses[0] assert_equal false, single1.check_validity assert_equal false, single1.check_validity(30) @@ -273,6 +270,8 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase assert_equal true, single2.check_validity assert_equal true, single2.check_validity(0, 500) assert_equal false, single2.check_validity(0, 200) + single3 = bres.responses[2] + assert_equal false, single3.check_validity end def test_response @@ -307,3 +306,5 @@ class OpenSSL::TestOCSP < OpenSSL::TestCase assert_equal res.to_der, res.dup.to_der end end + +end diff --git a/test/test_pair.rb b/test/test_pair.rb index a462891d..29d5c9bb 100644 --- a/test/test_pair.rb +++ b/test/test_pair.rb @@ -1,59 +1,46 @@ # frozen_string_literal: false require_relative 'utils' - -require 'socket' require_relative 'ut_eof' +if defined?(OpenSSL) + module OpenSSL::SSLPairM - def server - host = "127.0.0.1" - port = 0 - ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" - ctx.security_level = 0 - ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::Fixtures.pkey_dh("dh1024") } - tcps = create_tcp_server(host, port) - ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) - return ssls + def setup + svr_dn = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + ee_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ] + @svr_key = OpenSSL::TestUtils::Fixtures.pkey("rsa1024") + @svr_cert = issue_cert(svr_dn, @svr_key, 1, ee_exts, nil, nil) end - def client(port) + def ssl_pair host = "127.0.0.1" - ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" - ctx.security_level = 0 - s = create_tcp_client(host, port) - ssl = OpenSSL::SSL::SSLSocket.new(s, ctx) - ssl.connect - ssl.sync_close = true - ssl - end + tcps = create_tcp_server(host, 0) + port = tcps.connect_address.ip_port - def ssl_pair - ssls = server th = Thread.new { + sctx = OpenSSL::SSL::SSLContext.new + sctx.cert = @svr_cert + sctx.key = @svr_key + sctx.tmp_dh_callback = proc { OpenSSL::TestUtils::Fixtures.pkey_dh("dh1024") } + sctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION + ssls = OpenSSL::SSL::SSLServer.new(tcps, sctx) ns = ssls.accept ssls.close ns } - port = ssls.to_io.local_address.ip_port - c = client(port) + + tcpc = create_tcp_client(host, port) + c = OpenSSL::SSL::SSLSocket.new(tcpc) + c.connect s = th.value - if block_given? - begin - yield c, s - ensure - c.close unless c.closed? - s.close unless s.closed? - end - else - return c, s - end + + yield c, s ensure - if th&.alive? - th.kill - th.join - end + tcpc&.close + tcps&.close + s&.close end end @@ -83,23 +70,27 @@ end module OpenSSL::TestEOF1M def open_file(content) - s1, s2 = ssl_pair - th = Thread.new { s2 << content; s2.close } - yield s1 - ensure - th.join if th - s1.close + ssl_pair { |s1, s2| + begin + th = Thread.new { s2 << content; s2.close } + yield s1 + ensure + th&.join + end + } end end module OpenSSL::TestEOF2M def open_file(content) - s1, s2 = ssl_pair - th = Thread.new { s1 << content; s1.close } - yield s2 - ensure - th.join if th - s2.close + ssl_pair { |s1, s2| + begin + th = Thread.new { s1 << content; s1.close } + yield s2 + ensure + th&.join + end + } end end @@ -187,6 +178,27 @@ module OpenSSL::TestPairM } end + def test_multibyte_read_write + # German a umlaut + auml = [%w{ C3 A4 }.join('')].pack('H*') + auml.force_encoding(Encoding::UTF_8) + bsize = auml.bytesize + + ssl_pair { |s1, s2| + assert_equal bsize, s1.write(auml) + read = s2.read(bsize) + assert_equal Encoding::ASCII_8BIT, read.encoding + assert_equal bsize, read.bytesize + assert_equal auml, read.force_encoding(Encoding::UTF_8) + + s1.puts(auml) + read = s2.gets + assert_equal Encoding::ASCII_8BIT, read.encoding + assert_equal bsize + 1, read.bytesize + assert_equal auml + "\n", read.force_encoding(Encoding::UTF_8) + } + end + def test_read_nonblock ssl_pair {|s1, s2| err = nil @@ -206,7 +218,7 @@ module OpenSSL::TestPairM assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10) } assert_equal("def\n", ret) s1.close - sleep 0.1 + IO.select([s2]) assert_raise(EOFError) { s2.read_nonblock(10) } } end @@ -222,49 +234,71 @@ module OpenSSL::TestPairM assert_nothing_raised("[ruby-core:20298]") { ret = s2.read_nonblock(10, exception: false) } assert_equal("def\n", ret) s1.close - sleep 0.1 + IO.select([s2]) assert_equal(nil, s2.read_nonblock(10, exception: false)) } end - def write_nonblock(socket, meth, str) - ret = socket.send(meth, str) - ret.is_a?(Symbol) ? 0 : ret - end + def test_read_with_outbuf + ssl_pair { |s1, s2| + s1.write("abc\n") + buf = "" + ret = s2.read(2, buf) + assert_same ret, buf + assert_equal "ab", ret - def write_nonblock_no_ex(socket, str) - ret = socket.write_nonblock str, exception: false - ret.is_a?(Symbol) ? 0 : ret + buf = "garbage" + ret = s2.read(2, buf) + assert_same ret, buf + assert_equal "c\n", ret + + buf = "garbage" + assert_equal :wait_readable, s2.read_nonblock(100, buf, exception: false) + assert_equal "", buf + + s1.close + buf = "garbage" + assert_equal nil, s2.read(100, buf) + assert_equal "", buf + } end def test_write_nonblock ssl_pair {|s1, s2| - n = 0 - begin - n += write_nonblock s1, :write_nonblock, "a" * 100000 - n += write_nonblock s1, :write_nonblock, "b" * 100000 - n += write_nonblock s1, :write_nonblock, "c" * 100000 - n += write_nonblock s1, :write_nonblock, "d" * 100000 - n += write_nonblock s1, :write_nonblock, "e" * 100000 - n += write_nonblock s1, :write_nonblock, "f" * 100000 - rescue IO::WaitWritable + assert_equal 3, s1.write_nonblock("foo") + assert_equal "foo", s2.read(3) + + data = "x" * 16384 + written = 0 + while true + begin + written += s1.write_nonblock(data) + rescue IO::WaitWritable, IO::WaitReadable + break + end end - s1.close - assert_equal(n, s2.read.length) + assert written > 0 + assert_equal written, s2.read(written).bytesize } end def test_write_nonblock_no_exceptions ssl_pair {|s1, s2| - n = 0 - n += write_nonblock_no_ex s1, "a" * 100000 - n += write_nonblock_no_ex s1, "b" * 100000 - n += write_nonblock_no_ex s1, "c" * 100000 - n += write_nonblock_no_ex s1, "d" * 100000 - n += write_nonblock_no_ex s1, "e" * 100000 - n += write_nonblock_no_ex s1, "f" * 100000 - s1.close - assert_equal(n, s2.read.length) + assert_equal 3, s1.write_nonblock("foo", exception: false) + assert_equal "foo", s2.read(3) + + data = "x" * 16384 + written = 0 + while true + case ret = s1.write_nonblock(data, exception: false) + when :wait_readable, :wait_writable + break + else + written += ret + end + end + assert written > 0 + assert_equal written, s2.read(written).bytesize } end @@ -328,6 +362,15 @@ module OpenSSL::TestPairM } end + def test_write_multiple_arguments + ssl_pair {|s1, s2| + str1 = "foo"; str2 = "bar" + assert_equal 6, s1.write(str1, str2) + s1.close + assert_equal "foobar", s2.read + } + end + def test_partial_tls_record_read_nonblock ssl_pair { |s1, s2| # the beginning of a TLS record @@ -352,8 +395,8 @@ module OpenSSL::TestPairM def test_connect_accept_nonblock_no_exception ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.ciphers = "ADH" - ctx2.security_level = 0 + ctx2.cert = @svr_cert + ctx2.key = @svr_key ctx2.tmp_dh_callback = proc { OpenSSL::TestUtils::Fixtures.pkey_dh("dh1024") } sock1, sock2 = tcp_pair @@ -363,8 +406,6 @@ module OpenSSL::TestPairM assert_equal :wait_readable, accepted ctx1 = OpenSSL::SSL::SSLContext.new - ctx1.ciphers = "ADH" - ctx1.security_level = 0 s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) th = Thread.new do rets = [] @@ -402,8 +443,8 @@ module OpenSSL::TestPairM def test_connect_accept_nonblock ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" - ctx.security_level = 0 + ctx.cert = @svr_cert + ctx.key = @svr_key ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::Fixtures.pkey_dh("dh1024") } sock1, sock2 = tcp_pair @@ -426,8 +467,6 @@ module OpenSSL::TestPairM sleep 0.1 ctx = OpenSSL::SSL::SSLContext.new() - ctx.ciphers = "ADH" - ctx.security_level = 0 s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx) begin sleep 0.2 @@ -487,3 +526,5 @@ class OpenSSL::TestPairLowlevelSocket < OpenSSL::TestCase include OpenSSL::SSLPairLowlevelSocket include OpenSSL::TestPairM end + +end diff --git a/test/test_pkcs12.rb b/test/test_pkcs12.rb index 2a3b47f4..de8e35ed 100644 --- a/test/test_pkcs12.rb +++ b/test/test_pkcs12.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + module OpenSSL class TestPKCS12 < OpenSSL::TestCase def setup @@ -305,6 +307,7 @@ Kw4DAhoFAAQUYAuwVtGD1TdgbFK4Yal2XBgwUR4ECEawsN3rNaa6AgIIAA== end false end - end end + +end diff --git a/test/test_pkcs7.rb b/test/test_pkcs7.rb index 5320cc38..149d3b9b 100644 --- a/test/test_pkcs7.rb +++ b/test/test_pkcs7.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestPKCS7 < OpenSSL::TestCase def setup super @@ -279,3 +281,5 @@ END assert_equal(pki_message_content_pem, p7enc.to_pem) end end + +end diff --git a/test/test_pkey_dh.rb b/test/test_pkey_dh.rb index e7e76f97..77cdb0ab 100644 --- a/test/test_pkey_dh.rb +++ b/test/test_pkey_dh.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH) + class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase NEW_KEYLEN = 256 @@ -97,3 +99,5 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) end end + +end diff --git a/test/test_pkey_dsa.rb b/test/test_pkey_dsa.rb index 52ff8579..d6519498 100644 --- a/test/test_pkey_dsa.rb +++ b/test/test_pkey_dsa.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require_relative 'utils' -require 'base64' + +if defined?(OpenSSL) && defined?(OpenSSL::PKey::DSA) class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase def test_private @@ -195,3 +196,5 @@ fWLOqqkzFeRrYMDzUpl36XktY6Yq8EJYlW9pCMmBVNy/dQ== check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key]) end end + +end diff --git a/test/test_pkey_ec.rb b/test/test_pkey_ec.rb index 577ca868..dc5a14e6 100644 --- a/test/test_pkey_ec.rb +++ b/test/test_pkey_ec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require_relative 'utils' -if defined?(OpenSSL::PKey::EC) +if defined?(OpenSSL) && defined?(OpenSSL::PKey::EC) class OpenSSL::TestEC < OpenSSL::PKeyTestCase def test_ec_key diff --git a/test/test_pkey_rsa.rb b/test/test_pkey_rsa.rb index fed5aa9d..d9bea1a6 100644 --- a/test/test_pkey_rsa.rb +++ b/test/test_pkey_rsa.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false -require_relative 'utils' -require 'base64' +require_relative "utils" + +if defined?(OpenSSL) class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase def test_padding @@ -112,6 +113,39 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase } end + def test_sign_verify_pss + key = Fixtures.pkey("rsa1024") + data = "Sign me!" + invalid_data = "Sign me?" + + signature = key.sign_pss("SHA256", data, salt_length: 20, mgf1_hash: "SHA1") + assert_equal 128, signature.bytesize + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1") + assert_equal false, + key.verify_pss("SHA256", signature, invalid_data, salt_length: 20, mgf1_hash: "SHA1") + + signature = key.sign_pss("SHA256", data, salt_length: :digest, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: 32, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1") + assert_equal false, + key.verify_pss("SHA256", signature, data, salt_length: 20, mgf1_hash: "SHA1") + + signature = key.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: 94, mgf1_hash: "SHA1") + assert_equal true, + key.verify_pss("SHA256", signature, data, salt_length: :auto, mgf1_hash: "SHA1") + + assert_raise(OpenSSL::PKey::RSAError) { + key.sign_pss("SHA256", data, salt_length: 95, mgf1_hash: "SHA1") + } + end + def test_RSAPrivateKey rsa1024 = Fixtures.pkey("rsa1024") asn1 = OpenSSL::ASN1::Sequence([ @@ -267,3 +301,5 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase check_component(expected, key, [:n, :e, :d, :p, :q, :dmp1, :dmq1, :iqmp]) end end + +end diff --git a/test/test_random.rb b/test/test_random.rb index dd72d84d..d5a37454 100644 --- a/test/test_random.rb +++ b/test/test_random.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestRandom < OpenSSL::TestCase def test_random_bytes assert_equal("", OpenSSL::Random.random_bytes(0)) @@ -13,3 +15,5 @@ class OpenSSL::TestRandom < OpenSSL::TestCase assert_equal(12, OpenSSL::Random.pseudo_bytes(12).bytesize) end if OpenSSL::Random.methods.include?(:pseudo_bytes) end + +end diff --git a/test/test_ssl.rb b/test/test_ssl.rb index a519c6af..3b063d2e 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -1,8 +1,9 @@ # frozen_string_literal: false require_relative "utils" -class OpenSSL::TestSSL < OpenSSL::SSLTestCase +if defined?(OpenSSL) +class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_ctx_options ctx = OpenSSL::SSL::SSLContext.new @@ -33,7 +34,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase readwrite_loop(ctx, ssl) } - start_server(ctx_proc: ctx_proc, server_proc: server_proc) { |server, port| + start_server(ctx_proc: ctx_proc, server_proc: server_proc) { |port| begin sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new @@ -53,8 +54,89 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end + def test_add_certificate + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + } + start_server(ctx_proc: ctx_proc) do |port| + server_connect(port) { |ssl| + assert_equal @svr_cert.subject, ssl.peer_cert.subject + assert_equal [@svr_cert.subject, @ca_cert.subject], + ssl.peer_cert_chain.map(&:subject) + } + end + end + + def test_add_certificate_multiple_certs + pend "EC is not supported" unless defined?(OpenSSL::PKey::EC) + pend "TLS 1.2 is not supported" unless tls12_supported? + + # SSL_CTX_set0_chain() is needed for setting multiple certificate chains + add0_chain_supported = openssl?(1, 0, 2) + + if add0_chain_supported + ca2_key = Fixtures.pkey("rsa1024") + ca2_exts = [ + ["basicConstraints", "CA:TRUE", true], + ["keyUsage", "cRLSign, keyCertSign", true], + ] + ca2_dn = OpenSSL::X509::Name.parse_rfc2253("CN=CA2") + ca2_cert = issue_cert(ca2_dn, ca2_key, 123, ca2_exts, nil, nil) + else + # Use the same CA as @svr_cert + ca2_key = @ca_key; ca2_cert = @ca_cert + end + + ecdsa_key = Fixtures.pkey("p256") + exts = [ + ["keyUsage", "digitalSignature", false], + ] + ecdsa_dn = OpenSSL::X509::Name.parse_rfc2253("CN=localhost2") + ecdsa_cert = issue_cert(ecdsa_dn, ecdsa_key, 456, exts, ca2_cert, ca2_key) + + if !add0_chain_supported + # Testing the warning emitted when 'extra' chain is replaced + tctx = OpenSSL::SSL::SSLContext.new + tctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) + assert_warning(/set0_chain/) { + tctx.add_certificate(ecdsa_cert, ecdsa_key, [ca2_cert]) + } + end + + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.ecdh_curves = "P-256" unless openssl?(1, 0, 2) + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + EnvUtil.suppress_warning do # !add0_chain_supported + ctx.add_certificate(ecdsa_cert, ecdsa_key, [ca2_cert]) + end + } + start_server(ctx_proc: ctx_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 # TODO: We need this to force certificate type + ctx.ciphers = "aECDSA" + server_connect(port, ctx) { |ssl| + assert_equal ecdsa_cert.subject, ssl.peer_cert.subject + assert_equal [ecdsa_cert.subject, ca2_cert.subject], + ssl.peer_cert_chain.map(&:subject) + } + + ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = :TLS1_2 + ctx.ciphers = "aRSA" + server_connect(port, ctx) { |ssl| + assert_equal @svr_cert.subject, ssl.peer_cert.subject + assert_equal [@svr_cert.subject, @ca_cert.subject], + ssl.peer_cert_chain.map(&:subject) + } + end + end + def test_sysread_and_syswrite - start_server { |server, port| + start_server { |port| server_connect(port) { |ssl| str = "x" * 100 + "\n" ssl.syswrite(str) @@ -70,7 +152,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_sync_close - start_server { |server, port| + start_server { |port| begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -95,7 +177,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_copy_stream - start_server do |server, port| + start_server do |port| server_connect(port) do |ssl| IO.pipe do |r, w| str = "hello world\n" @@ -110,21 +192,16 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_client_auth_failure vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ignore_listener_error: true) { |server, port| - sock = TCPSocket.new("127.0.0.1", port) - ssl = OpenSSL::SSL::SSLSocket.new(sock) - ssl.sync_close = true - begin - assert_handshake_error { ssl.connect } - ensure - ssl.close - end + start_server(verify_mode: vflag, ignore_listener_error: true) { |port| + assert_handshake_error { + server_connect(port) { |ssl| ssl.puts("abc"); ssl.gets } + } } end def test_client_auth_success vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag) { |server, port| + start_server(verify_mode: vflag) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.key = @cli_key ctx.cert = @cli_cert @@ -151,19 +228,21 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_client_auth_public_key vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ignore_listener_error: true) do |server, port| + start_server(verify_mode: vflag, ignore_listener_error: true) do |port| assert_raise(ArgumentError) { ctx = OpenSSL::SSL::SSLContext.new ctx.key = @cli_key.public_key ctx.cert = @cli_cert - server_connect(port, ctx) { } + server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } } ctx = OpenSSL::SSL::SSLContext.new ctx.client_cert_cb = Proc.new{ |ssl| [@cli_cert, @cli_key.public_key] } - assert_handshake_error { server_connect(port, ctx) } + assert_handshake_error { + server_connect(port, ctx) { |ssl| ssl.puts("abc"); ssl.gets } + } end end @@ -173,7 +252,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end vflag = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT - start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |server, port| + start_server(verify_mode: vflag, ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new client_ca_from_server = nil ctx.client_cert_cb = Proc.new do |sslconn| @@ -185,8 +264,8 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_read_nonblock_without_session - OpenSSL::TestUtils.silent do - start_server(start_immediately: false) { |server, port| + EnvUtil.suppress_warning do + start_server(start_immediately: false) { |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.sync_close = true @@ -204,26 +283,21 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_starttls server_proc = -> (ctx, ssl) { - begin - while line = ssl.gets - if line =~ /^STARTTLS$/ - ssl.write("x") - ssl.flush - ssl.accept - next - end - ssl.write(line) + while line = ssl.gets + if line =~ /^STARTTLS$/ + ssl.write("x") + ssl.flush + ssl.accept + break end - rescue OpenSSL::SSL::SSLError - rescue IOError - ensure - ssl.close rescue nil + ssl.write(line) end + readwrite_loop(ctx, ssl) } EnvUtil.suppress_warning do # read/write on not started session start_server(start_immediately: false, - server_proc: server_proc) { |server, port| + server_proc: server_proc) { |port| begin sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) @@ -246,7 +320,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_parallel - start_server { |server, port| + start_server { |port| ssls = [] 10.times{ sock = TCPSocket.new("127.0.0.1", port) @@ -267,7 +341,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_verify_result - start_server(ignore_listener_error: true) { |server, port| + start_server(ignore_listener_error: true) { |port| sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -281,7 +355,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end } - start_server { |server, port| + start_server { |port| sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -299,7 +373,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end } - start_server(ignore_listener_error: true) { |server, port| + start_server(ignore_listener_error: true) { |port| sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -319,7 +393,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_exception_in_verify_callback_is_ignored - start_server(ignore_listener_error: true) { |server, port| + start_server(ignore_listener_error: true) { |port| sock = TCPSocket.new("127.0.0.1", port) ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER @@ -330,7 +404,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) ssl.sync_close = true begin - OpenSSL::TestUtils.silent do + EnvUtil.suppress_warning do # SSLError, not RuntimeError assert_raise(OpenSSL::SSL::SSLError) { ssl.connect } end @@ -355,13 +429,17 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_post_connect_check_with_anon_ciphers + pend "TLS 1.2 is not supported" unless tls12_supported? + ctx_proc = -> ctx { + ctx.ssl_version = :TLSv1_2 ctx.ciphers = "aNULL" ctx.security_level = 0 } - start_server(ctx_proc: ctx_proc) { |server, port| + start_server(ctx_proc: ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_2 ctx.ciphers = "aNULL" ctx.security_level = 0 server_connect(port, ctx) { |ssl| @@ -375,7 +453,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase def test_post_connection_check sslerr = OpenSSL::SSL::SSLError - start_server { |server, port| + start_server { |port| server_connect(port) { |ssl| assert_raise(sslerr){ssl.post_connection_check("localhost.localdomain")} assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} @@ -396,7 +474,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ["subjectAltName","IP:127.0.0.1",false], ] @svr_cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key) - start_server { |server, port| + start_server { |port| server_connect(port) { |ssl| assert(ssl.post_connection_check("localhost.localdomain")) assert(ssl.post_connection_check("127.0.0.1")) @@ -416,7 +494,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ["subjectAltName","DNS:*.localdomain",false], ] @svr_cert = issue_cert(@svr, @svr_key, 5, exts, @ca_cert, @ca_key) - start_server { |server, port| + start_server { |port| server_connect(port) { |ssl| assert(ssl.post_connection_check("localhost.localdomain")) assert_raise(sslerr){ssl.post_connection_check("127.0.0.1")} @@ -619,48 +697,44 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_tlsext_hostname - ctx3 = OpenSSL::SSL::SSLContext.new - ctx3.ciphers = "ADH" - ctx3.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } - ctx3.security_level = 0 - assert_not_predicate ctx3, :frozen? + fooctx = OpenSSL::SSL::SSLContext.new + fooctx.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } + fooctx.cert = @cli_cert + fooctx.key = @cli_key - ctx_proc = -> ctx { - ctx.ciphers = "ALL:!aNULL" + ctx_proc = proc { |ctx| ctx.servername_cb = proc { |ssl, servername| case servername when "foo.example.com" - ctx3 + fooctx when "bar.example.com" nil else - raise "unknown hostname" + raise "unreachable" end } } - start_server(ctx_proc: ctx_proc) do |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "ALL" - ctx.security_level = 0 - + start_server(ctx_proc: ctx_proc) do |port| sock = TCPSocket.new("127.0.0.1", port) begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.hostname = "foo.example.com" ssl.connect - assert_match (/^ADH-/), ssl.cipher[0], "the context returned by servername_cb is used" - assert_predicate ctx3, :frozen? + assert_equal @cli_cert.serial, ssl.peer_cert.serial + assert_predicate fooctx, :frozen? ensure + ssl&.close sock.close end sock = TCPSocket.new("127.0.0.1", port) begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.hostname = "bar.example.com" ssl.connect - assert_not_match (/^A(EC)?DH-/), ssl.cipher[0], "the original context is used" + assert_equal @svr_cert.serial, ssl.peer_cert.serial ensure + ssl&.close sock.close end end @@ -670,9 +744,9 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase hostname = 'example.org' ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.ciphers = "aNULL" + ctx2.cert = @svr_cert + ctx2.key = @svr_key ctx2.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } - ctx2.security_level = 0 ctx2.servername_cb = lambda { |args| Object.new } sock1, sock2 = socketpair @@ -680,8 +754,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) ctx1 = OpenSSL::SSL::SSLContext.new - ctx1.ciphers = "aNULL" - ctx1.security_level = 0 s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) s1.hostname = hostname @@ -712,7 +784,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ctx.key = @svr_key } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |server, port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_hostname = true ctx.cert_store = OpenSSL::X509::Store.new @@ -746,7 +818,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end def test_connect_certificate_verify_failed_exception_message - start_server(ignore_listener_error: true) { |server, port| + start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.set_params assert_raise_with_message(OpenSSL::SSL::SSLError, /self signed/) { @@ -758,7 +830,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase ctx.cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key, not_before: Time.now-100, not_after: Time.now-10) } - start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |server, port| + start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |port| store = OpenSSL::X509::Store.new store.add_cert(@ca_cert) ctx = OpenSSL::SSL::SSLContext.new @@ -769,31 +841,6 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end - def test_multibyte_read_write - #German a umlaut - auml = [%w{ C3 A4 }.join('')].pack('H*') - auml.force_encoding(Encoding::UTF_8) - - [10, 1000, 100000].each {|i| - str = nil - num_written = nil - server_proc = Proc.new {|ctx, ssl| - cmp = ssl.read - raw_size = cmp.size - cmp.force_encoding(Encoding::UTF_8) - assert_equal(str, cmp) - assert_equal(num_written, raw_size) - ssl.close - } - start_server(server_proc: server_proc) { |server, port| - server_connect(port) { |ssl| - str = auml * i - num_written = ssl.write(str) - } - } - } - end - def test_unset_OP_ALL ctx_proc = Proc.new { |ctx| # If OP_DONT_INSERT_EMPTY_FRAGMENTS is not defined, this test is @@ -801,7 +848,7 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase # But it also degrades gracefully, so keep it ctx.options = OpenSSL::SSL::OP_ALL } - start_server(ctx_proc: ctx_proc) { |server, port| + start_server(ctx_proc: ctx_proc) { |port| server_connect(port) { |ssl| ssl.puts('hello') assert_equal("hello\n", ssl.gets) @@ -809,112 +856,241 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase } end -if OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1) && OpenSSL::SSL::SSLContext::METHODS.include?(:SSLv3) - - def test_forbid_ssl_v3_for_client - ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3 } - start_server_version(:SSLv23, ctx_proc) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :SSLv3 - assert_handshake_error { server_connect(port, ctx) } - } - end + def check_supported_protocol_versions + possible_versions = [ + OpenSSL::SSL::SSL3_VERSION, + OpenSSL::SSL::TLS1_VERSION, + OpenSSL::SSL::TLS1_1_VERSION, + OpenSSL::SSL::TLS1_2_VERSION, + # OpenSSL 1.1.1 + defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION, + ].compact + + # Prepare for testing & do sanity check + supported = [] + possible_versions.each do |ver| + catch(:unsupported) { + ctx_proc = proc { |ctx| + begin + ctx.min_version = ctx.max_version = ver + rescue ArgumentError, OpenSSL::SSL::SSLError + throw :unsupported + end + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + begin + server_connect(port) { } + rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET + else + supported << ver + end + end + } + end + assert_not_empty supported - def test_forbid_ssl_v3_from_server - start_server_version(:SSLv3) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv3 - assert_handshake_error { server_connect(port, ctx) } - } + supported end -end - -if OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_1) && OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1) + def test_set_params_min_version + supported = check_supported_protocol_versions + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) - def test_tls_v1_1 - start_server_version(:TLSv1_1) { |server, port| - server_connect(port) { |ssl| assert_equal("TLSv1.1", ssl.ssl_version) } - } + if supported.include?(OpenSSL::SSL::SSL3_VERSION) + # SSLContext#set_params properly disables SSL 3.0 by default + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::SSL3_VERSION + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params(cert_store: store, verify_hostname: false) + assert_handshake_error { server_connect(port, ctx) { } } + } + end end - def test_forbid_tls_v1_for_client - ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1 } - start_server_version(:SSLv23, ctx_proc) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1 - assert_handshake_error { server_connect(port, ctx) } + def test_minmax_version + supported = check_supported_protocol_versions + + # name: The string that would be returned by SSL_get_version() + # method: The version-specific method name (if any) + vmap = { + OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" }, + OpenSSL::SSL::SSL3_VERSION => { name: "SSLv3", method: "SSLv3" }, + OpenSSL::SSL::TLS1_VERSION => { name: "TLSv1", method: "TLSv1" }, + OpenSSL::SSL::TLS1_1_VERSION => { name: "TLSv1.1", method: "TLSv1_1" }, + OpenSSL::SSL::TLS1_2_VERSION => { name: "TLSv1.2", method: "TLSv1_2" }, + # OpenSSL 1.1.1 + defined?(OpenSSL::SSL::TLS1_3_VERSION) && OpenSSL::SSL::TLS1_3_VERSION => + { name: "TLSv1.3", method: nil }, } - end - def test_forbid_tls_v1_from_server - start_server_version(:TLSv1) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1 - assert_handshake_error { server_connect(port, ctx) } - } - end + # Server enables a single version + supported.each do |ver| + ctx_proc = proc { |ctx| ctx.min_version = ctx.max_version = ver } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + supported.each do |cver| + # Client enables a single version + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.min_version = ctx1.max_version = cver + if ver == cver + server_connect(port, ctx1) { |ssl| + assert_equal vmap[cver][:name], ssl.ssl_version + } + else + assert_handshake_error { server_connect(port, ctx1) { } } + end -end + # There is no version-specific SSL methods for TLS 1.3 + if cver <= OpenSSL::SSL::TLS1_2_VERSION + # Client enables a single version using #ssl_version= + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.ssl_version = vmap[cver][:method] + if ver == cver + server_connect(port, ctx2) { |ssl| + assert_equal vmap[cver][:name], ssl.ssl_version + } + else + assert_handshake_error { server_connect(port, ctx2) { } } + end + end + end -if OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_2) && OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_1) + # Client enables all supported versions + ctx3 = OpenSSL::SSL::SSLContext.new + ctx3.min_version = ctx3.max_version = nil + server_connect(port, ctx3) { |ssl| + assert_equal vmap[ver][:name], ssl.ssl_version + } + } + end - def test_tls_v1_2 - start_server_version(:TLSv1_2) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2_client - server_connect(port, ctx) { |ssl| assert_equal("TLSv1.2", ssl.ssl_version) } - } - end + if supported.size == 1 + pend "More than one protocol version must be supported" + end - def test_forbid_tls_v1_1_for_client - ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1 } - start_server_version(:SSLv23, ctx_proc) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_1 - assert_handshake_error { server_connect(port, ctx) } + # Server sets min_version (earliest is disabled) + sver = supported[1] + ctx_proc = proc { |ctx| ctx.min_version = sver } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + supported.each do |cver| + # Client sets min_version + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.min_version = cver + server_connect(port, ctx1) { |ssl| + assert_equal vmap[supported.last][:name], ssl.ssl_version + } + + # Client sets max_version + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.max_version = cver + if cver >= sver + server_connect(port, ctx2) { |ssl| + assert_equal vmap[cver][:name], ssl.ssl_version + } + else + assert_handshake_error { server_connect(port, ctx2) { } } + end + end } - end - def test_forbid_tls_v1_1_from_server - start_server_version(:TLSv1_1) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_1 - assert_handshake_error { server_connect(port, ctx) } + # Server sets max_version (latest is disabled) + sver = supported[-2] + ctx_proc = proc { |ctx| ctx.max_version = sver } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + supported.each do |cver| + # Client sets min_version + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.min_version = cver + if cver <= sver + server_connect(port, ctx1) { |ssl| + assert_equal vmap[sver][:name], ssl.ssl_version + } + else + assert_handshake_error { server_connect(port, ctx1) { } } + end + + # Client sets max_version + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.max_version = cver + server_connect(port, ctx2) { |ssl| + if cver >= sver + assert_equal vmap[sver][:name], ssl.ssl_version + else + assert_equal vmap[cver][:name], ssl.ssl_version + end + } + end } end - def test_forbid_tls_v1_2_for_client - ctx_proc = Proc.new { |ctx| ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2 } - start_server_version(:SSLv23, ctx_proc) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ssl_version = :TLSv1_2 - assert_handshake_error { server_connect(port, ctx) } - } + def test_options_disable_versions + # Note: Use of these OP_* flags has been deprecated since OpenSSL 1.1.0. + supported = check_supported_protocol_versions + + if supported.include?(OpenSSL::SSL::TLS1_1_VERSION) && + supported.include?(OpenSSL::SSL::TLS1_2_VERSION) + # Server disables ~ TLS 1.1 + ctx_proc = proc { |ctx| + ctx.options |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 | + OpenSSL::SSL::OP_NO_TLSv1 | OpenSSL::SSL::OP_NO_TLSv1_1 + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + # Client only supports TLS 1.1 + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.min_version = ctx1.max_version = OpenSSL::SSL::TLS1_1_VERSION + assert_handshake_error { server_connect(port, ctx1) { } } + + # Client only supports TLS 1.2 + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.min_version = ctx2.max_version = OpenSSL::SSL::TLS1_2_VERSION + assert_nothing_raised { server_connect(port, ctx2) { } } + } + + # Server only supports TLS 1.1 + ctx_proc = proc { |ctx| + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) { |port| + # Client disables TLS 1.1 + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.options |= OpenSSL::SSL::OP_NO_TLSv1_1 + assert_handshake_error { server_connect(port, ctx1) { } } + + # Client disables TLS 1.2 + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.options |= OpenSSL::SSL::OP_NO_TLSv1_2 + assert_nothing_raised { server_connect(port, ctx2) { } } + } + else + pend "TLS 1.1 and TLS 1.2 must be supported; skipping" + end end - def test_forbid_tls_v1_2_from_server - start_server_version(:TLSv1_2) { |server, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.options = OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_TLSv1_2 - assert_handshake_error { server_connect(port, ctx) } + def test_ssl_methods_constant + EnvUtil.suppress_warning { # Deprecated in v2.1.0 + base = [:TLSv1_2, :TLSv1_1, :TLSv1, :SSLv3, :SSLv2, :SSLv23] + base.each do |name| + assert_include OpenSSL::SSL::SSLContext::METHODS, name + assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_client" + assert_include OpenSSL::SSL::SSLContext::METHODS, :"#{name}_server" + end } end -end - def test_renegotiation_cb num_handshakes = 0 renegotiation_cb = Proc.new { |ssl| num_handshakes += 1 } ctx_proc = Proc.new { |ctx| ctx.renegotiation_cb = renegotiation_cb } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:SSLv23, ctx_proc) { |port| server_connect(port) { |ssl| assert_equal(1, num_handshakes) } } end -if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 +if openssl?(1, 0, 2) || libressl? def test_alpn_protocol_selection_ary advertised = ["http/1.1", "spdy/2"] ctx_proc = Proc.new { |ctx| @@ -923,7 +1099,7 @@ if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 } ctx.alpn_protocols = advertised } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:SSLv23, ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.alpn_protocols = advertised server_connect(port, ctx) { |ssl| @@ -936,14 +1112,13 @@ if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 sock1, sock2 = socketpair ctx1 = OpenSSL::SSL::SSLContext.new - ctx1.ciphers = "aNULL" - ctx1.security_level = 0 + ctx1.cert = @svr_cert + ctx1.key = @svr_key + ctx1.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } ctx1.alpn_select_cb = -> (protocols) { nil } ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.ciphers = "aNULL" - ctx2.security_level = 0 ctx2.alpn_protocols = ["http/1.1"] ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) @@ -962,13 +1137,15 @@ if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 end end -if OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) - # NPN may be disabled by OpenSSL configure option - def test_npn_protocol_selection_ary + pend "TLS 1.2 is not supported" unless tls12_supported? + pend "NPN is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) + pend "LibreSSL 2.6 has broken NPN functions" if libressl?(2, 6, 1) + advertised = ["http/1.1", "spdy/2"] - ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } - start_server_version(:SSLv23, ctx_proc) { |server, port| + ctx_proc = proc { |ctx| ctx.npn_protocols = advertised } + start_server_version(:TLSv1_2, ctx_proc) { |port| selector = lambda { |which| ctx = OpenSSL::SSL::SSLContext.new ctx.npn_select_cb = -> (protocols) { protocols.send(which) } @@ -982,13 +1159,18 @@ if OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) end def test_npn_protocol_selection_enum + pend "TLS 1.2 is not supported" unless tls12_supported? + pend "NPN is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) + pend "LibreSSL 2.6 has broken NPN functions" if libressl?(2, 6, 1) + advertised = Object.new def advertised.each yield "http/1.1" yield "spdy/2" end ctx_proc = Proc.new { |ctx| ctx.npn_protocols = advertised } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:TLSv1_2, ctx_proc) { |port| selector = lambda { |selected, which| ctx = OpenSSL::SSL::SSLContext.new ctx.npn_select_cb = -> (protocols) { protocols.to_a.send(which) } @@ -1002,8 +1184,13 @@ if OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) end def test_npn_protocol_selection_cancel + pend "TLS 1.2 is not supported" unless tls12_supported? + pend "NPN is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) + pend "LibreSSL 2.6 has broken NPN functions" if libressl?(2, 6, 1) + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:TLSv1_2, ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.npn_select_cb = -> (protocols) { raise RuntimeError.new } assert_raise(RuntimeError) { server_connect(port, ctx) } @@ -1011,8 +1198,13 @@ if OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) end def test_npn_advertised_protocol_too_long + pend "TLS 1.2 is not supported" unless tls12_supported? + pend "NPN is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) + pend "LibreSSL 2.6 has broken NPN functions" if libressl?(2, 6, 1) + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["a" * 256] } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:TLSv1_2, ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.npn_select_cb = -> (protocols) { protocols.first } assert_handshake_error { server_connect(port, ctx) } @@ -1020,32 +1212,24 @@ if OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) end def test_npn_selected_protocol_too_long + pend "TLS 1.2 is not supported" unless tls12_supported? + pend "NPN is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:npn_select_cb) + pend "LibreSSL 2.6 has broken NPN functions" if libressl?(2, 6, 1) + ctx_proc = Proc.new { |ctx| ctx.npn_protocols = ["http/1.1"] } - start_server_version(:SSLv23, ctx_proc) { |server, port| + start_server_version(:TLSv1_2, ctx_proc) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.npn_select_cb = -> (protocols) { "a" * 256 } assert_handshake_error { server_connect(port, ctx) } } end -end - - def test_invalid_shutdown_by_gc - assert_nothing_raised { - start_server { |server, port| - 10.times { - sock = TCPSocket.new("127.0.0.1", port) - ssl = OpenSSL::SSL::SSLSocket.new(sock) - GC.start - ssl.connect - sock.close - } - } - } - end - def test_close_after_socket_close - start_server { |server, port| + server_proc = proc { |ctx, ssl| + # Do nothing + } + start_server(server_proc: server_proc) { |port| sock = TCPSocket.new("127.0.0.1", port) ssl = OpenSSL::SSL::SSLSocket.new(sock) ssl.sync_close = true @@ -1066,69 +1250,124 @@ end } end - def test_close_and_socket_close_while_connecting - # test it doesn't cause a segmentation fault - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "aNULL" - ctx.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } - ctx.security_level = 0 + def test_get_ephemeral_key + # OpenSSL >= 1.0.2 + unless OpenSSL::SSL::SSLSocket.method_defined?(:tmp_key) + pend "SSL_get_server_tmp_key() is not supported" + end - sock1, sock2 = socketpair - ssl1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx) - ssl2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx) + if tls12_supported? + # kRSA + ctx_proc1 = proc { |ctx| + ctx.ssl_version = :TLSv1_2 + ctx.ciphers = "kRSA" + } + start_server(ctx_proc: ctx_proc1) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_2 + ctx.ciphers = "kRSA" + server_connect(port, ctx) { |ssl| assert_nil ssl.tmp_key } + end + end - t = Thread.new { ssl1.connect } - ssl2.accept + if defined?(OpenSSL::PKey::DH) && tls12_supported? + # DHE + # TODO: How to test this with TLS 1.3? + ctx_proc2 = proc { |ctx| + ctx.ssl_version = :TLSv1_2 + ctx.ciphers = "EDH" + } + start_server(ctx_proc: ctx_proc2) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ssl_version = :TLSv1_2 + ctx.ciphers = "EDH" + server_connect(port, ctx) { |ssl| + assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + } + end + end - ssl1.close - sock1.close - t.value rescue nil - ensure - ssl1.close if ssl1 - ssl2.close if ssl2 - sock1.close if sock1 - sock2.close if sock2 + if defined?(OpenSSL::PKey::EC) + # ECDHE + ctx_proc3 = proc { |ctx| + ctx.ciphers = "DEFAULT:!kRSA:!kEDH" + ctx.ecdh_curves = "P-256" + } + start_server(ctx_proc: ctx_proc3) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.ciphers = "DEFAULT:!kRSA:!kEDH" + server_connect(port, ctx) { |ssl| + assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key + } + end + end end - def test_get_ephemeral_key - return unless OpenSSL::SSL::SSLSocket.method_defined?(:tmp_key) - pkey = OpenSSL::PKey - ciphers = { - 'ECDHE-RSA-AES128-SHA' => (pkey::EC if defined?(pkey::EC)), - 'DHE-RSA-AES128-SHA' => (pkey::DH if defined?(pkey::DH)), - 'AES128-SHA' => nil + def test_fallback_scsv + pend "Fallback SCSV is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:enable_fallback_scsv) + + start_server do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + # Here is OK + # TLS1.2 supported and this is what we ask the first time + server_connect(port, ctx) + end + + ctx_proc = proc { |ctx| + ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION } - conf_proc = Proc.new { |ctx| ctx.ciphers = 'ALL' } - start_server(ctx_proc: conf_proc) do |server, port| - ciphers.each do |cipher, ephemeral| - ctx = OpenSSL::SSL::SSLContext.new - begin - ctx.ciphers = cipher - rescue OpenSSL::SSL::SSLError => e - next if /no cipher match/ =~ e.message - raise - end - server_connect(port, ctx) do |ssl| - if ephemeral - assert_instance_of(ephemeral, ssl.tmp_key) - else - assert_nil(ssl.tmp_key) - end - end - end + start_server(ctx_proc: ctx_proc) do |port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.enable_fallback_scsv + ctx.max_version = OpenSSL::SSL::TLS1_1_VERSION + # Here is OK too + # TLS1.2 not supported, fallback to TLS1.1 and signaling the fallback + # Server doesn't support better, so connection OK + server_connect(port, ctx) + end + + # Here is not OK + # TLS1.2 is supported, fallback to TLS1.1 (downgrade attack) and signaling the fallback + # Server support better, so refuse the connection + sock1, sock2 = socketpair + begin + ctx1 = OpenSSL::SSL::SSLContext.new + s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.enable_fallback_scsv + ctx2.max_version = OpenSSL::SSL::TLS1_1_VERSION + s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) + t = Thread.new { + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) { + s2.connect + } + } + assert_raise_with_message(OpenSSL::SSL::SSLError, /inappropriate fallback/) { + s1.accept + } + t.join + ensure + sock1.close + sock2.close end end def test_dh_callback + pend "TLS 1.2 is not supported" unless tls12_supported? + called = false ctx_proc = -> ctx { + ctx.ssl_version = :TLSv1_2 ctx.ciphers = "DH:!NULL" ctx.tmp_dh_callback = ->(*args) { called = true Fixtures.pkey_dh("dh1024") } } - start_server(ctx_proc: ctx_proc) do |server, port| + start_server(ctx_proc: ctx_proc) do |port| server_connect(port) { |ssl| assert called, "dh callback should be called" if ssl.respond_to?(:tmp_key) @@ -1139,11 +1378,14 @@ end end def test_connect_works_when_setting_dh_callback_to_nil + pend "TLS 1.2 is not supported" unless tls12_supported? + ctx_proc = -> ctx { + ctx.ssl_version = :TLSv1_2 ctx.ciphers = "DH:!NULL" # use DH ctx.tmp_dh_callback = nil } - start_server(ctx_proc: ctx_proc) do |server, port| + start_server(ctx_proc: ctx_proc) do |port| EnvUtil.suppress_warning { # uses default callback assert_nothing_raised { server_connect(port) { } @@ -1152,73 +1394,55 @@ end end end - def test_ecdh_callback - return unless OpenSSL::SSL::SSLContext.instance_methods.include?(:tmp_ecdh_callback) + def test_tmp_ecdh_callback + pend "EC is disabled" unless defined?(OpenSSL::PKey::EC) + pend "tmp_ecdh_callback is not supported" unless \ + OpenSSL::SSL::SSLContext.method_defined?(:tmp_ecdh_callback) + pend "LibreSSL 2.6 has broken SSL_CTX_set_tmp_ecdh_callback()" \ + if libressl?(2, 6, 1) + EnvUtil.suppress_warning do # tmp_ecdh_callback is deprecated (2016-05) - begin - called = false - ctx2 = OpenSSL::SSL::SSLContext.new - ctx2.ciphers = "ECDH" - # OpenSSL 1.1.0 doesn't have tmp_ecdh_callback so this shouldn't be required - ctx2.security_level = 0 - ctx2.tmp_ecdh_callback = ->(*args) { + called = false + ctx_proc = -> ctx { + ctx.ciphers = "DEFAULT:!kRSA:!kEDH" + ctx.tmp_ecdh_callback = -> (*args) { called = true OpenSSL::PKey::EC.new "prime256v1" } - - sock1, sock2 = socketpair - - s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2) - ctx1 = OpenSSL::SSL::SSLContext.new - ctx1.ciphers = "ECDH" - ctx1.security_level = 0 - - s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1) - th = Thread.new do - s1.connect - end - - s2.accept - assert called, 'ecdh callback should be called' - rescue OpenSSL::SSL::SSLError => e - if e.message =~ /no cipher match/ - pend "ECDH cipher not supported." - else - raise e - end - ensure - th.join if th - s1.close if s1 - s2.close if s2 - sock1.close if sock1 - sock2.close if sock2 + } + start_server(ctx_proc: ctx_proc) do |port| + server_connect(port) { |s| + assert called, "tmp_ecdh_callback should be called" + } end end end def test_ecdh_curves + pend "EC is disabled" unless defined?(OpenSSL::PKey::EC) + ctx_proc = -> ctx { - begin - ctx.ciphers = "ECDH:!NULL" - rescue OpenSSL::SSL::SSLError - pend "ECDH is not enabled in this OpenSSL" if $!.message =~ /no cipher match/ - raise - end + # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 + ctx.ciphers = "DEFAULT:!kRSA:!kEDH" ctx.ecdh_curves = "P-384:P-521" } - start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |server, port| + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new ctx.ecdh_curves = "P-256:P-384" # disable P-521 for OpenSSL >= 1.0.2 server_connect(port, ctx) { |ssl| - assert ssl.cipher[0].start_with?("ECDH"), "ECDH should be used" - if ssl.respond_to?(:tmp_key) + cs = ssl.cipher[0] + if /\ATLS/ =~ cs # Is TLS 1.3 is used? assert_equal "secp384r1", ssl.tmp_key.group.curve_name + else + assert_match (/\AECDH/), cs + if ssl.respond_to?(:tmp_key) + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + end end } - if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 && - !OpenSSL::OPENSSL_VERSION.include?("LibreSSL") + if openssl?(1, 0, 2) || libressl?(2, 5, 1) ctx = OpenSSL::SSL::SSLContext.new ctx.ecdh_curves = "P-256" @@ -1245,11 +1469,24 @@ end return end assert_equal(1, ctx.security_level) - # assert_raise(OpenSSL::SSL::SSLError) { ctx.key = Fixtures.pkey("dsa512") } - # ctx.key = Fixtures.pkey("rsa1024") - # ctx.security_level = 2 - # assert_raise(OpenSSL::SSL::SSLError) { ctx.key = Fixtures.pkey("rsa1024") } - pend "FIXME: SSLContext#key= currently does not raise because SSL_CTX_use_certificate() is delayed" + + dsa512 = Fixtures.pkey("dsa512") + dsa512_cert = issue_cert(@svr, dsa512, 50, [], @ca_cert, @ca_key) + rsa1024 = Fixtures.pkey("rsa1024") + rsa1024_cert = issue_cert(@svr, rsa1024, 51, [], @ca_cert, @ca_key) + + assert_raise(OpenSSL::SSL::SSLError) { + # 512 bit DSA key is rejected because it offers < 80 bits of security + ctx.add_certificate(dsa512_cert, dsa512) + } + assert_nothing_raised { + ctx.add_certificate(rsa1024_cert, rsa1024) + } + ctx.security_level = 2 + assert_raise(OpenSSL::SSL::SSLError) { + # < 112 bits of security + ctx.add_certificate(rsa1024_cert, rsa1024) + } end def test_dup @@ -1267,7 +1504,7 @@ end def test_freeze_calls_setup bug = "[ruby/openssl#85]" - start_server(ignore_listener_error: true) { |server, port| + start_server(ignore_listener_error: true) { |port| ctx = OpenSSL::SSL::SSLContext.new ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER ctx.freeze @@ -1293,7 +1530,7 @@ end ) end - def server_connect(port, ctx=nil) + def server_connect(port, ctx = nil) sock = TCPSocket.new("127.0.0.1", port) ssl = ctx ? OpenSSL::SSL::SSLSocket.new(sock, ctx) : OpenSSL::SSL::SSLSocket.new(sock) ssl.sync_close = true @@ -1315,3 +1552,5 @@ end } end end + +end diff --git a/test/test_ssl_session.rb b/test/test_ssl_session.rb index f89732ab..2cb46cd2 100644 --- a/test/test_ssl_session.rb +++ b/test/test_ssl_session.rb @@ -1,55 +1,15 @@ # frozen_string_literal: false require_relative "utils" -class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase - def test_session_equals - session = OpenSSL::SSL::Session.new <<-SESSION ------BEGIN SSL SESSION PARAMETERS----- -MIIDFgIBAQICAwEEAgA5BCCY3pW6iTkPoD5SENuztz/gZjhvey6XnHbsxd22k0Ol -dgQw8uaN3hCRnlhoIKPWInCFzrp/tQsDRFs9jDjc9pwpy/oKHmJdQQMQA1g8FYnO -gpdVoQYCBE52ikKiBAICASyjggKOMIICijCCAXKgAwIBAgIBAjANBgkqhkiG9w0B -AQUFADA9MRMwEQYKCZImiZPyLGQBGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVi -eS1sYW5nMQswCQYDVQQDDAJDQTAeFw0xMTA5MTkwMDE4MTBaFw0xMTA5MTkwMDQ4 -MTBaMEQxEzARBgoJkiaJk/IsZAEZFgNvcmcxGTAXBgoJkiaJk/IsZAEZFglydWJ5 -LWxhbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw -gYkCgYEAy8LEsNRApz7U/j5DoB4XBgO9Z8Atv5y/OVQRp0ag8Tqo1YewsWijxEWB -7JOATwpBN267U4T1nPZIxxEEO7n/WNa2ws9JWsjah8ssEBFSxZqdXKSLf0N4Hi7/ -GQ/aYoaMCiQ8jA4jegK2FJmXM71uPe+jFN/peeBOpRfyXxRFOYcCAwEAAaMSMBAw -DgYDVR0PAQH/BAQDAgWgMA0GCSqGSIb3DQEBBQUAA4IBAQARC7GP7InX1t7VEXz2 -I8RI57S0/HSJL4fDIYP3zFpitHX1PZeo+7XuzMilvPjjBo/ky9Jzo8TYiY+N+JEz -mY/A/zPA4ZsJ7KYj6/FEdIc/vRlS0CvsbClbNjw1jl/PoB2FLr2b3uuBcZEsyZeP -yq154ijq37Ajf8K5Mi5FgshoP41BPtRPj+VVf61rv1IcEnNWdDCS6DR4XsaNC+zt -G6AqCqkytIXWRuDw6n6vYLF3A/tn2sldLo7/scY0PMDNbo63O/LTxkDHmPhSkD68 -8m9SsMeTR+RCiDEZWFPVcAH/8mDfi+5k8uN3qS+gOU/PPrmHGgl5ykiSFgqs4v61 -tddwpBAEDjcwMzA5NTYzMTU1MzAwpQMCARM= ------END SSL SESSION PARAMETERS----- - SESSION - - start_server(ignore_listener_error: true) { |_, port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT - ctx.session_id_context = self.object_id.to_s - - sock = TCPSocket.new '127.0.0.1', port - begin - ssl = OpenSSL::SSL::SSLSocket.new sock, ctx - ssl.session = session - - assert_equal session, ssl.session - ensure - sock.close - end - } - end +if defined?(OpenSSL) +class OpenSSL::TestSSLSession < OpenSSL::SSLTestCase def test_session - Timeout.timeout(5) do - start_server do |server, port| - sock = TCPSocket.new("127.0.0.1", port) - ctx = OpenSSL::SSL::SSLContext.new - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - ssl.sync_close = true - ssl.connect + pend "TLS 1.2 is not supported" unless tls12_supported? + + ctx_proc = proc { |ctx| ctx.ssl_version = :TLSv1_2 } + start_server(ctx_proc: ctx_proc) do |port| + server_connect_with_session(port, nil, nil) { |ssl| session = ssl.session assert(session == OpenSSL::SSL::Session.new(session.to_pem)) assert(session == OpenSSL::SSL::Session.new(ssl)) @@ -66,8 +26,7 @@ tddwpBAEDjcwMzA5NTYzMTU1MzAwpQMCARM= pem.gsub!(/-----(BEGIN|END) SSL SESSION PARAMETERS-----/, '').gsub!(/[\r\n]+/m, '') assert_equal(session.to_der, pem.unpack('m*')[0]) assert_not_nil(session.to_text) - ssl.close - end + } end end @@ -150,220 +109,265 @@ __EOS__ assert(OpenSSL::SSL::Session.new(DUMMY_SESSION)) end - def test_client_session - last_session = nil - start_server do |server, port| - 2.times do - sock = TCPSocket.new("127.0.0.1", port) - ctx = OpenSSL::SSL::SSLContext.new - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - ssl.sync_close = true - ssl.session = last_session if last_session - ssl.connect + def test_resumption + non_resumable = nil + start_server { |port| + server_connect_with_session(port, nil, nil) { |ssl| + non_resumable = ssl.session + } + } - session = ssl.session - if last_session - assert(ssl.session_reused?) - assert_equal(session.id, last_session.id) - assert_equal(session.to_pem, last_session.to_pem) - assert_equal(session.to_der, last_session.to_der) - # Older version of OpenSSL may not be consistent. Look up which versions later. - assert_equal(session.to_text, last_session.to_text) - else - assert(!ssl.session_reused?) - end - last_session = session + ctx_proc = proc { |ctx| + ctx.options &= ~OpenSSL::SSL::OP_NO_TICKET + # Disable server-side session cache which is enabled by default + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF + } + start_server(ctx_proc: ctx_proc) do |port| + sess1 = server_connect_with_session(port, nil, nil) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal false, ssl.session_reused? + ssl.session + } - str = "x" * 100 + "\n" - ssl.puts(str) - assert_equal(str, ssl.gets) + server_connect_with_session(port, nil, non_resumable) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal false, ssl.session_reused? + } - ssl.close - end + server_connect_with_session(port, nil, sess1) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal true, ssl.session_reused? + } end end - def test_server_session - connections = 0 - saved_session = nil + def test_server_session_cache + pend "TLS 1.2 is not supported" unless tls12_supported? - ctx_proc = Proc.new do |ctx, ssl| -# add test for session callbacks here + ctx_proc = Proc.new do |ctx| + ctx.ssl_version = :TLSv1_2 + ctx.options |= OpenSSL::SSL::OP_NO_TICKET end + connections = nil + saved_session = nil server_proc = Proc.new do |ctx, ssl| - session = ssl.session stats = ctx.session_cache_stats case connections when 0 - assert_equal(stats[:cache_num], 1) - assert_equal(stats[:cache_hits], 0) - assert_equal(stats[:cache_misses], 0) - assert(!ssl.session_reused?) + assert_equal false, ssl.session_reused? + assert_equal 1, stats[:cache_num] + assert_equal 0, stats[:cache_hits] + assert_equal 0, stats[:cache_misses] when 1 - assert_equal(stats[:cache_num], 1) - assert_equal(stats[:cache_hits], 1) - assert_equal(stats[:cache_misses], 0) - assert(ssl.session_reused?) - ctx.session_remove(session) - saved_session = session.to_der + assert_equal true, ssl.session_reused? + assert_equal 1, stats[:cache_num] + assert_equal 1, stats[:cache_hits] + assert_equal 0, stats[:cache_misses] + + saved_session = ssl.session + assert_equal true, ctx.session_remove(ssl.session) when 2 - assert_equal(stats[:cache_num], 1) - assert_equal(stats[:cache_hits], 1) - assert_equal(stats[:cache_misses], 1) - assert(!ssl.session_reused?) - ctx.session_add(OpenSSL::SSL::Session.new(saved_session)) + assert_equal false, ssl.session_reused? + assert_equal 1, stats[:cache_num] + assert_equal 1, stats[:cache_hits] + assert_equal 1, stats[:cache_misses] + + assert_equal true, ctx.session_add(saved_session.dup) when 3 - assert_equal(stats[:cache_num], 2) - assert_equal(stats[:cache_hits], 2) - assert_equal(stats[:cache_misses], 1) - assert(ssl.session_reused?) + assert_equal true, ssl.session_reused? + assert_equal 2, stats[:cache_num] + assert_equal 2, stats[:cache_hits] + assert_equal 1, stats[:cache_misses] + ctx.flush_sessions(Time.now + 10000) when 4 - assert_equal(stats[:cache_num], 1) - assert_equal(stats[:cache_hits], 2) - assert_equal(stats[:cache_misses], 2) - assert(!ssl.session_reused?) - ctx.session_add(OpenSSL::SSL::Session.new(saved_session)) + assert_equal false, ssl.session_reused? + assert_equal 1, stats[:cache_num] + assert_equal 2, stats[:cache_hits] + assert_equal 2, stats[:cache_misses] + + assert_equal true, ctx.session_add(saved_session.dup) end - connections += 1 readwrite_loop(ctx, ssl) end - first_session = nil - start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |server, port| + start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |port| + first_session = nil 10.times do |i| - sock = TCPSocket.new("127.0.0.1", port) - ctx = OpenSSL::SSL::SSLContext.new - # disable RFC4507 support - ctx.options = OpenSSL::SSL::OP_NO_TICKET - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - ssl.sync_close = true - ssl.session = first_session if first_session - ssl.connect - - session = ssl.session - if first_session - case i - when 1; assert(ssl.session_reused?) - when 2; assert(!ssl.session_reused?) - when 3; assert(ssl.session_reused?) - when 4; assert(!ssl.session_reused?) - when 5..10; assert(ssl.session_reused?) + connections = i + server_connect_with_session(port, nil, first_session) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + first_session ||= ssl.session + + case connections + when 0; + when 1; assert_equal true, ssl.session_reused? + when 2; assert_equal false, ssl.session_reused? + when 3; assert_equal true, ssl.session_reused? + when 4; assert_equal false, ssl.session_reused? + when 5..9; assert_equal true, ssl.session_reused? end - end - first_session ||= session - - str = "x" * 100 + "\n" - ssl.puts(str) - assert_equal(str, ssl.gets) - - ssl.close + } end end end - def test_ctx_client_session_cb - called = {} - ctx = OpenSSL::SSL::SSLContext.new - ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + # Skipping tests that use session_remove_cb by default because it may cause + # deadlock. + TEST_SESSION_REMOVE_CB = ENV["OSSL_TEST_ALL"] == "1" - ctx.session_new_cb = lambda { |ary| - sock, sess = ary - called[:new] = [sock, sess] - } + def test_ctx_client_session_cb + pend "TLS 1.2 is not supported" unless tls12_supported? - ctx.session_remove_cb = lambda { |ary| - ctx, sess = ary - called[:remove] = [ctx, sess] - # any resulting value is OK (ignored) - } + ctx_proc = proc { |ctx| ctx.ssl_version = :TLSv1_2 } + start_server(ctx_proc: ctx_proc) do |port| + called = {} + ctx = OpenSSL::SSL::SSLContext.new + ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT + ctx.session_new_cb = lambda { |ary| + sock, sess = ary + called[:new] = [sock, sess] + } + if TEST_SESSION_REMOVE_CB + ctx.session_remove_cb = lambda { |ary| + ctx, sess = ary + called[:remove] = [ctx, sess] + # any resulting value is OK (ignored) + } + end - start_server do |server, port| - sock = TCPSocket.new("127.0.0.1", port) - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) - ssl.sync_close = true - ssl.connect + server_connect_with_session(port, ctx, nil) { |ssl| assert_equal(1, ctx.session_cache_stats[:cache_num]) assert_equal(1, ctx.session_cache_stats[:connect_good]) assert_equal([ssl, ssl.session], called[:new]) assert(ctx.session_remove(ssl.session)) assert(!ctx.session_remove(ssl.session)) - assert_equal([ctx, ssl.session], called[:remove]) - ssl.close - ensure - sock.close if !sock.closed? - end + if TEST_SESSION_REMOVE_CB + assert_equal([ctx, ssl.session], called[:remove]) + end + } end end def test_ctx_server_session_cb - called = {} + pend "TLS 1.2 is not supported" unless tls12_supported? - ctx_proc = Proc.new { |ctx, ssl| - ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER - ctx.options = OpenSSL::SSL::OP_NO_TICKET - last_server_session = nil + connections = nil + called = {} + sctx = nil + ctx_proc = Proc.new { |ctx| + sctx = ctx + ctx.ssl_version = :TLSv1_2 + ctx.options |= OpenSSL::SSL::OP_NO_TICKET # get_cb is called whenever a client proposed to resume a session but # the session could not be found in the internal session cache. + last_server_session = nil ctx.session_get_cb = lambda { |ary| - sess, data = ary - if last_server_session - called[:get2] = [sess, data] - last_server_session + _sess, data = ary + called[:get] = data + + if connections == 2 + last_server_session.dup else - called[:get1] = [sess, data] - last_server_session = sess nil end } ctx.session_new_cb = lambda { |ary| - sock, sess = ary - called[:new] = [sock, sess] - # SSL server doesn't cache sessions so get_cb is called next time. - ctx.session_remove(sess) + _sock, sess = ary + called[:new] = sess + last_server_session = sess } - ctx.session_remove_cb = lambda { |ary| - ctx, sess = ary - called[:remove] = [ctx, sess] - } + if TEST_SESSION_REMOVE_CB + ctx.session_remove_cb = lambda { |ary| + _ctx, sess = ary + called[:remove] = sess + } + end } + start_server(ctx_proc: ctx_proc) do |port| + connections = 0 + sess0 = server_connect_with_session(port, nil, nil) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal false, ssl.session_reused? + ssl.session + } + assert_nil called[:get] + assert_not_nil called[:new] + assert_equal sess0.id, called[:new].id + if TEST_SESSION_REMOVE_CB + assert_nil called[:remove] + end + called.clear + + # Internal cache hit + connections = 1 + server_connect_with_session(port, nil, sess0.dup) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal true, ssl.session_reused? + ssl.session + } + assert_nil called[:get] + assert_nil called[:new] + if TEST_SESSION_REMOVE_CB + assert_nil called[:remove] + end + called.clear - server_proc = Proc.new { |c, ssl| - ssl.session - c.session_cache_stats - readwrite_loop(c, ssl) - } - start_server(ctx_proc: ctx_proc, server_proc: server_proc) do |server, port| - last_client_session = nil - 3.times do - sock = TCPSocket.new("127.0.0.1", port) - begin - ssl = OpenSSL::SSL::SSLSocket.new(sock, OpenSSL::SSL::SSLContext.new()) - ssl.sync_close = true - ssl.session = last_client_session if last_client_session - ssl.connect - last_client_session = ssl.session - ssl.close - Timeout.timeout(5) do - Thread.pass until called.key?(:new) - assert(called.delete(:new)) - Thread.pass until called.key?(:remove) - assert(called.delete(:remove)) - end - ensure - sock.close if !sock.closed? + sctx.flush_sessions(Time.now + 10000) + if TEST_SESSION_REMOVE_CB + assert_not_nil called[:remove] + assert_equal sess0.id, called[:remove].id + end + called.clear + + # External cache hit + connections = 2 + sess2 = server_connect_with_session(port, nil, sess0.dup) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + if !ssl.session_reused? && openssl?(1, 1, 0) && !openssl?(1, 1, 0, 7) + # OpenSSL >= 1.1.0, < 1.1.0g + pend "External session cache is not working; " \ + "see https://github.com/openssl/openssl/pull/4014" end + assert_equal true, ssl.session_reused? + ssl.session + } + assert_equal sess0.id, sess2.id + assert_equal sess0.id, called[:get] + assert_nil called[:new] + if TEST_SESSION_REMOVE_CB + assert_nil called[:remove] + end + called.clear + + sctx.flush_sessions(Time.now + 10000) + if TEST_SESSION_REMOVE_CB + assert_not_nil called[:remove] + assert_equal sess0.id, called[:remove].id + end + called.clear + + # Cache miss + connections = 3 + sess3 = server_connect_with_session(port, nil, sess0.dup) { |ssl| + ssl.puts("abc"); assert_equal "abc\n", ssl.gets + assert_equal false, ssl.session_reused? + ssl.session + } + assert_not_equal sess0.id, sess3.id + assert_equal sess0.id, called[:get] + assert_not_nil called[:new] + assert_equal sess3.id, called[:new].id + if TEST_SESSION_REMOVE_CB + assert_nil called[:remove] end end - assert(called[:get1]) - assert(called[:get2]) end def test_dup @@ -371,4 +375,21 @@ __EOS__ sess_dup = sess_orig.dup assert_equal(sess_orig.to_der, sess_dup.to_der) end + + private + + def server_connect_with_session(port, ctx = nil, sess = nil) + sock = TCPSocket.new("127.0.0.1", port) + ctx ||= OpenSSL::SSL::SSLContext.new + ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx) + ssl.session = sess if sess + ssl.sync_close = true + ssl.connect + yield ssl if block_given? + ensure + ssl&.close + sock&.close + end +end + end diff --git a/test/test_x509attr.rb b/test/test_x509attr.rb index 249c1593..c6c48e86 100644 --- a/test/test_x509attr.rb +++ b/test/test_x509attr.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestX509Attribute < OpenSSL::TestCase def test_new ef = OpenSSL::X509::ExtensionFactory.new @@ -60,4 +62,23 @@ class OpenSSL::TestX509Attribute < OpenSSL::TestCase attr = OpenSSL::X509::Attribute.new("challengePassword", val) assert_equal(attr.to_der, attr.dup.to_der) end + + def test_eq + val1 = OpenSSL::ASN1::Set([ + OpenSSL::ASN1::UTF8String("abc123") + ]) + attr1 = OpenSSL::X509::Attribute.new("challengePassword", val1) + attr2 = OpenSSL::X509::Attribute.new("challengePassword", val1) + ef = OpenSSL::X509::ExtensionFactory.new + val2 = OpenSSL::ASN1::Set.new([OpenSSL::ASN1::Sequence.new([ + ef.create_extension("keyUsage", "keyCertSign", true) + ])]) + attr3 = OpenSSL::X509::Attribute.new("extReq", val2) + + assert_equal false, attr1 == 12345 + assert_equal true, attr1 == attr2 + assert_equal false, attr1 == attr3 + end +end + end diff --git a/test/test_x509cert.rb b/test/test_x509cert.rb index 5a992119..40a5b0ad 100644 --- a/test/test_x509cert.rb +++ b/test/test_x509cert.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestX509Certificate < OpenSSL::TestCase def setup super @@ -167,6 +169,26 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase } end + def test_eq + now = Time.now + cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil, + not_before: now, not_after: now + 3600) + cert1 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + not_before: now, not_after: now + 3600) + cert2 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + not_before: now, not_after: now + 3600) + cert3 = issue_cert(@ee1, @rsa2048, 3, [], cacert, @rsa1024, + not_before: now, not_after: now + 3600) + cert4 = issue_cert(@ee1, @rsa2048, 2, [], cacert, @rsa1024, + digest: "sha512", not_before: now, not_after: now + 3600) + + assert_equal false, cert1 == 12345 + assert_equal true, cert1 == cert2 + assert_equal false, cert1 == cert3 + assert_equal false, cert1 == cert4 + assert_equal false, cert3 == cert4 + end + private def certificate_error_returns_false @@ -175,3 +197,5 @@ class OpenSSL::TestX509Certificate < OpenSSL::TestCase false end end + +end diff --git a/test/test_x509crl.rb b/test/test_x509crl.rb index fd7b562a..03fdf64d 100644 --- a/test/test_x509crl.rb +++ b/test/test_x509crl.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestX509CRL < OpenSSL::TestCase def setup super @@ -195,6 +197,58 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase assert_equal(false, crl.verify(@dsa512)) end + def test_revoked_to_der + # revokedCertificates SEQUENCE OF SEQUENCE { + # userCertificate CertificateSerialNumber, + # revocationDate Time, + # crlEntryExtensions Extensions OPTIONAL + # -- if present, version MUST be v2 + # } OPTIONAL, + + now = Time.utc(2000, 1, 1) + rev1 = OpenSSL::X509::Revoked.new + rev1.serial = 123 + rev1.time = now + ext = OpenSSL::X509::Extension.new("CRLReason", OpenSSL::ASN1::Enumerated(1)) + rev1.extensions = [ext] + asn1 = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(123), + OpenSSL::ASN1::UTCTime(now), + OpenSSL::ASN1::Sequence([ext.to_der]) + ]) + + assert_equal asn1.to_der, rev1.to_der + end + + def test_eq + now = Time.now + + cacert = issue_cert(@ca, @rsa1024, 1, [], nil, nil) + crl1 = issue_crl([], 1, now, now + 3600, [], cacert, @rsa1024, "sha256") + rev1 = OpenSSL::X509::Revoked.new.tap { |rev| + rev.serial = 1 + rev.time = now + } + crl1.add_revoked(rev1) + crl2 = OpenSSL::X509::CRL.new(crl1.to_der) + + # CRL + assert_equal false, crl1 == 12345 + assert_equal true, crl1 == crl2 + rev2 = OpenSSL::X509::Revoked.new.tap { |rev| + rev.serial = 2 + rev.time = now + } + crl2.add_revoked(rev2) + assert_equal false, crl1 == crl2 + + # Revoked + assert_equal false, rev1 == 12345 + assert_equal true, rev1 == crl2.revoked[0] + assert_equal false, rev1 == crl2.revoked[1] + assert_equal true, rev2 == crl2.revoked[1] + end + private def crl_error_returns_false @@ -203,3 +257,5 @@ class OpenSSL::TestX509CRL < OpenSSL::TestCase false end end + +end diff --git a/test/test_x509ext.rb b/test/test_x509ext.rb index d9cde1bd..91ce202f 100644 --- a/test/test_x509ext.rb +++ b/test/test_x509ext.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestX509Extension < OpenSSL::TestCase def setup super @@ -73,4 +75,17 @@ class OpenSSL::TestX509Extension < OpenSSL::TestCase assert_equal(@basic_constraints.to_der, ext.to_der) assert_equal(ext.to_der, ext.dup.to_der) end + + def test_eq + ext1 = OpenSSL::X509::Extension.new(@basic_constraints.to_der) + ef = OpenSSL::X509::ExtensionFactory.new + ext2 = ef.create_extension("basicConstraints", "critical, CA:TRUE, pathlen:2") + ext3 = ef.create_extension("basicConstraints", "critical, CA:TRUE") + + assert_equal false, ext1 == 12345 + assert_equal true, ext1 == ext2 + assert_equal false, ext1 == ext3 + end +end + end diff --git a/test/test_x509name.rb b/test/test_x509name.rb index b2196628..2d92e645 100644 --- a/test/test_x509name.rb +++ b/test/test_x509name.rb @@ -1,7 +1,9 @@ -# coding: US-ASCII +# coding: ASCII-8BIT # frozen_string_literal: false require_relative 'utils' +if defined?(OpenSSL) + class OpenSSL::TestX509Name < OpenSSL::TestCase def setup super @@ -146,33 +148,28 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase end def test_s_parse - dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org" + dn = "/DC=org/DC=ruby-lang/CN=www.ruby-lang.org/1.2.3.4.5.6=A=BCD" name = OpenSSL::X509::Name.parse(dn) assert_equal(dn, name.to_s) ary = name.to_a - assert_equal("DC", ary[0][0]) - assert_equal("DC", ary[1][0]) - assert_equal("CN", ary[2][0]) - assert_equal("org", ary[0][1]) - assert_equal("ruby-lang", ary[1][1]) - assert_equal("www.ruby-lang.org", ary[2][1]) - assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) - assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) - assert_equal(OpenSSL::ASN1::UTF8STRING, ary[2][2]) + assert_equal [ + ["DC", "org", OpenSSL::ASN1::IA5STRING], + ["DC", "ruby-lang", OpenSSL::ASN1::IA5STRING], + ["CN", "www.ruby-lang.org", OpenSSL::ASN1::UTF8STRING], + ["1.2.3.4.5.6", "A=BCD", OpenSSL::ASN1::UTF8STRING], + ], ary - dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org" + dn2 = "DC=org, DC=ruby-lang, CN=www.ruby-lang.org, 1.2.3.4.5.6=A=BCD" name = OpenSSL::X509::Name.parse(dn2) - ary = name.to_a assert_equal(dn, name.to_s) - assert_equal("org", ary[0][1]) - assert_equal("ruby-lang", ary[1][1]) - assert_equal("www.ruby-lang.org", ary[2][1]) + assert_equal ary, name.to_a name = OpenSSL::X509::Name.parse(dn2, @obj_type_tmpl) ary = name.to_a assert_equal(OpenSSL::ASN1::IA5STRING, ary[0][2]) assert_equal(OpenSSL::ASN1::IA5STRING, ary[1][2]) assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[2][2]) + assert_equal(OpenSSL::ASN1::PRINTABLESTRING, ary[3][2]) end def test_s_parse_rfc2253 @@ -354,16 +351,53 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase assert_equal("CN=unya,OU=b+OU=a,O=ruby-lang", dn.dup.to_s(OpenSSL::X509::Name::RFC2253)) end + def test_to_s + dn = [ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "フー, バー"], + ] + name = OpenSSL::X509::Name.new + dn.each { |x| name.add_entry(*x) } + + assert_equal "/DC=org/DC=ruby-lang/" \ + "CN=\\xE3\\x83\\x95\\xE3\\x83\\xBC, \\xE3\\x83\\x90\\xE3\\x83\\xBC", + name.to_s + # OpenSSL escapes characters with MSB by default + assert_equal \ + "CN=\\E3\\83\\95\\E3\\83\\BC\\, \\E3\\83\\90\\E3\\83\\BC," \ + "DC=ruby-lang,DC=org", + name.to_s(OpenSSL::X509::Name::RFC2253) + assert_equal "DC = org, DC = ruby-lang, " \ + "CN = \"\\E3\\83\\95\\E3\\83\\BC, \\E3\\83\\90\\E3\\83\\BC\"", + name.to_s(OpenSSL::X509::Name::ONELINE) + end + + def test_to_utf8 + dn = [ + ["DC", "org"], + ["DC", "ruby-lang"], + ["CN", "フー, バー"], + ] + name = OpenSSL::X509::Name.new + dn.each { |x| name.add_entry(*x) } + + str = name.to_utf8 + expected = "CN=フー\\, バー,DC=ruby-lang,DC=org".force_encoding("UTF-8") + assert_equal expected, str + assert_equal Encoding.find("UTF-8"), str.encoding + end + def test_equals2 - n1 = OpenSSL::X509::Name.parse 'CN=a' - n2 = OpenSSL::X509::Name.parse 'CN=a' + n1 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' + n2 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' assert_equal n1, n2 end def test_spaceship - n1 = OpenSSL::X509::Name.parse 'CN=a' - n2 = OpenSSL::X509::Name.parse 'CN=b' + n1 = OpenSSL::X509::Name.parse_rfc2253 'CN=a' + n2 = OpenSSL::X509::Name.parse_rfc2253 'CN=b' assert_equal(-1, n1 <=> n2) end @@ -403,3 +437,5 @@ class OpenSSL::TestX509Name < OpenSSL::TestCase assert_equal(name.to_der, name.dup.to_der) end end + +end diff --git a/test/test_x509req.rb b/test/test_x509req.rb index 88156220..2c447ccd 100644 --- a/test/test_x509req.rb +++ b/test/test_x509req.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestX509Request < OpenSSL::TestCase def setup super @@ -139,6 +141,16 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase assert_equal(req.to_der, req.dup.to_der) end + def test_eq + req1 = issue_csr(0, @dn, @rsa1024, "sha1") + req2 = issue_csr(0, @dn, @rsa1024, "sha1") + req3 = issue_csr(0, @dn, @rsa1024, "sha256") + + assert_equal false, req1 == 12345 + assert_equal true, req1 == req2 + assert_equal false, req1 == req3 + end + private def request_error_returns_false @@ -147,3 +159,5 @@ class OpenSSL::TestX509Request < OpenSSL::TestCase false end end + +end diff --git a/test/test_x509store.rb b/test/test_x509store.rb index 0009813d..6412249b 100644 --- a/test/test_x509store.rb +++ b/test/test_x509store.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require_relative "utils" +if defined?(OpenSSL) + class OpenSSL::TestX509Store < OpenSSL::TestCase def setup super @@ -207,7 +209,7 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase end def test_set_errors - return if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000 + return if openssl?(1, 1, 0) || libressl? now = Time.now ca1_cert = issue_cert(@ca1, @rsa2048, 1, [], nil, nil) store = OpenSSL::X509::Store.new @@ -235,3 +237,5 @@ class OpenSSL::TestX509Store < OpenSSL::TestCase assert_raise(NoMethodError) { ctx.dup } end end + +end diff --git a/test/ut_eof.rb b/test/ut_eof.rb index 6de41c4a..bd62fd50 100644 --- a/test/ut_eof.rb +++ b/test/ut_eof.rb @@ -1,6 +1,8 @@ # frozen_string_literal: false require 'test/unit' +if defined?(OpenSSL) + module OpenSSL::TestEOF def test_eof_0 open_file("") {|f| @@ -127,3 +129,5 @@ module OpenSSL::TestEOF end end end + +end diff --git a/test/utils.rb b/test/utils.rb index 54f75f25..a3599490 100644 --- a/test/utils.rb +++ b/test/utils.rb @@ -30,11 +30,12 @@ if ENV["OSSL_MDEBUG"] == "1" end require "test/unit" -require 'tempfile' -require "rbconfig" +require "tempfile" require "socket" require "envutil" +if defined?(OpenSSL) + module OpenSSL::TestUtils module Fixtures module_function @@ -66,7 +67,7 @@ module OpenSSL::TestUtils cert.serial = serial cert.subject = dn cert.issuer = issuer.subject - cert.public_key = key.public_key + cert.public_key = key now = Time.now cert.not_before = not_before || now - 3600 cert.not_after = not_after || now + 3600 @@ -117,183 +118,191 @@ module OpenSSL::TestUtils OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase end - def silent - begin - back, $VERBOSE = $VERBOSE, nil - yield - ensure - $VERBOSE = back - end + def openssl?(major = nil, minor = nil, fix = nil, patch = 0) + return false if OpenSSL::OPENSSL_VERSION.include?("LibreSSL") + return true unless major + OpenSSL::OPENSSL_VERSION_NUMBER >= + major * 0x10000000 + minor * 0x100000 + fix * 0x1000 + patch * 0x10 end - class OpenSSL::TestCase < Test::Unit::TestCase - include OpenSSL::TestUtils - extend OpenSSL::TestUtils + def libressl?(major = nil, minor = nil, fix = nil) + version = OpenSSL::OPENSSL_VERSION.scan(/LibreSSL (\d+)\.(\d+)\.(\d+).*/)[0] + return false unless version + !major || (version.map(&:to_i) <=> [major, minor, fix]) >= 0 + end +end - def setup - if ENV["OSSL_GC_STRESS"] == "1" - GC.stress = true - end - end +class OpenSSL::TestCase < Test::Unit::TestCase + include OpenSSL::TestUtils + extend OpenSSL::TestUtils - def teardown - if ENV["OSSL_GC_STRESS"] == "1" - GC.stress = false - end - # OpenSSL error stack must be empty - assert_equal([], OpenSSL.errors) + def setup + if ENV["OSSL_GC_STRESS"] == "1" + GC.stress = true end end - class OpenSSL::SSLTestCase < OpenSSL::TestCase - RUBY = EnvUtil.rubybin - ITERATIONS = ($0 == __FILE__) ? 100 : 10 - - def setup - super - @ca_key = Fixtures.pkey("rsa2048") - @svr_key = Fixtures.pkey("rsa1024") - @cli_key = Fixtures.pkey("dsa1024") - @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") - @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") - @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") - ca_exts = [ - ["basicConstraints","CA:TRUE",true], - ["keyUsage","cRLSign,keyCertSign",true], - ] - ee_exts = [ - ["keyUsage","keyEncipherment,digitalSignature",true], - ] - @ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil) - @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key) - @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key) - @server = nil + def teardown + if ENV["OSSL_GC_STRESS"] == "1" + GC.stress = false end + # OpenSSL error stack must be empty + assert_equal([], OpenSSL.errors) + end +end - def readwrite_loop(ctx, ssl) - while line = ssl.gets - ssl.write(line) - end - rescue OpenSSL::SSL::SSLError - rescue IOError - ensure - ssl.close rescue nil - end +class OpenSSL::SSLTestCase < OpenSSL::TestCase + RUBY = EnvUtil.rubybin + ITERATIONS = ($0 == __FILE__) ? 100 : 10 - def server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads) - loop do - ssl = nil - begin - readable, = IO.select([ssls, stop_pipe_r]) - if readable.include? stop_pipe_r - return - end - ssl = ssls.accept - rescue OpenSSL::SSL::SSLError, Errno::ECONNRESET - if ignore_listener_error - retry - else - raise - end - end + def setup + super + @ca_key = Fixtures.pkey("rsa2048") + @svr_key = Fixtures.pkey("rsa1024") + @cli_key = Fixtures.pkey("rsa2048") + @ca = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=CA") + @svr = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + @cli = OpenSSL::X509::Name.parse("/DC=org/DC=ruby-lang/CN=localhost") + ca_exts = [ + ["basicConstraints","CA:TRUE",true], + ["keyUsage","cRLSign,keyCertSign",true], + ] + ee_exts = [ + ["keyUsage","keyEncipherment,digitalSignature",true], + ] + @ca_cert = issue_cert(@ca, @ca_key, 1, ca_exts, nil, nil) + @svr_cert = issue_cert(@svr, @svr_key, 2, ee_exts, @ca_cert, @ca_key) + @cli_cert = issue_cert(@cli, @cli_key, 3, ee_exts, @ca_cert, @ca_key) + @server = nil + end - th = Thread.start do - server_proc.call(ctx, ssl) - end - threads << th - end - rescue Errno::EBADF, IOError, Errno::EINVAL, Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET - if !ignore_listener_error - raise - end + def tls12_supported? + ctx = OpenSSL::SSL::SSLContext.new + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + true + rescue + end + + def readwrite_loop(ctx, ssl) + while line = ssl.gets + ssl.write(line) end + end - def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true, - ctx_proc: nil, server_proc: method(:readwrite_loop), - ignore_listener_error: false, &block) - IO.pipe {|stop_pipe_r, stop_pipe_w| - store = OpenSSL::X509::Store.new - store.add_cert(@ca_cert) - store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT - ctx = OpenSSL::SSL::SSLContext.new - ctx.cert_store = store - ctx.cert = @svr_cert - ctx.key = @svr_key - ctx.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } - begin - ctx.ecdh_curves = "P-256" - rescue NotImplementedError - end - ctx.verify_mode = verify_mode - ctx_proc.call(ctx) if ctx_proc + def start_server(verify_mode: OpenSSL::SSL::VERIFY_NONE, start_immediately: true, + ctx_proc: nil, server_proc: method(:readwrite_loop), + ignore_listener_error: false, &block) + IO.pipe {|stop_pipe_r, stop_pipe_w| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx = OpenSSL::SSL::SSLContext.new + ctx.cert_store = store + ctx.cert = @svr_cert + ctx.key = @svr_key + ctx.tmp_dh_callback = proc { Fixtures.pkey_dh("dh1024") } + ctx.verify_mode = verify_mode + ctx_proc.call(ctx) if ctx_proc + + Socket.do_not_reverse_lookup = true + tcps = TCPServer.new("127.0.0.1", 0) + port = tcps.connect_address.ip_port - Socket.do_not_reverse_lookup = true - tcps = nil - tcps = TCPServer.new("127.0.0.1", 0) - port = tcps.connect_address.ip_port + ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) + ssls.start_immediately = start_immediately - ssls = OpenSSL::SSL::SSLServer.new(tcps, ctx) - ssls.start_immediately = start_immediately + threads = [] + begin + server_thread = Thread.new do + begin + loop do + begin + readable, = IO.select([ssls, stop_pipe_r]) + break if readable.include? stop_pipe_r + ssl = ssls.accept + rescue OpenSSL::SSL::SSLError, IOError, Errno::EBADF, Errno::EINVAL, + Errno::ECONNABORTED, Errno::ENOTSOCK, Errno::ECONNRESET + retry if ignore_listener_error + raise + end - threads = [] - begin - server = Thread.new do - begin - server_loop(ctx, ssls, stop_pipe_r, ignore_listener_error, server_proc, threads) - ensure - tcps.close + th = Thread.new do + begin + server_proc.call(ctx, ssl) + ensure + ssl.close + end + true + end + threads << th end + ensure + tcps.close end - threads.unshift server - - $stderr.printf("SSL server started: pid=%d port=%d\n", $$, port) if $DEBUG + end - client = Thread.new do - begin - block.call(server, port.to_i) - ensure - stop_pipe_w.close - end + client_thread = Thread.new do + begin + block.call(port) + ensure + # Stop accepting new connection + stop_pipe_w.close + server_thread.join end - threads.unshift client - ensure - assert_join_threads(threads) end - } - end + threads.unshift client_thread + ensure + # Terminate existing connections. If a thread did 'pend', re-raise it. + pend = nil + threads.each { |th| + begin + th.join(10) or + th.raise(RuntimeError, "[start_server] thread did not exit in 10 secs") + rescue (defined?(MiniTest::Skip) ? MiniTest::Skip : Test::Unit::PendedError) + # MiniTest::Skip is for the Ruby tree + pend = $! + rescue Exception + end + } + raise pend if pend + assert_join_threads(threads) + end + } end +end - class OpenSSL::PKeyTestCase < OpenSSL::TestCase - def check_component(base, test, keys) - keys.each { |comp| - assert_equal base.send(comp), test.send(comp) - } - end +class OpenSSL::PKeyTestCase < OpenSSL::TestCase + def check_component(base, test, keys) + keys.each { |comp| + assert_equal base.send(comp), test.send(comp) + } + end - def dup_public(key) - case key - when OpenSSL::PKey::RSA - rsa = OpenSSL::PKey::RSA.new - rsa.set_key(key.n, key.e, nil) - rsa - when OpenSSL::PKey::DSA - dsa = OpenSSL::PKey::DSA.new - dsa.set_pqg(key.p, key.q, key.g) - dsa.set_key(key.pub_key, nil) - dsa - when OpenSSL::PKey::DH - dh = OpenSSL::PKey::DH.new - dh.set_pqg(key.p, nil, key.g) - dh + def dup_public(key) + case key + when OpenSSL::PKey::RSA + rsa = OpenSSL::PKey::RSA.new + rsa.set_key(key.n, key.e, nil) + rsa + when OpenSSL::PKey::DSA + dsa = OpenSSL::PKey::DSA.new + dsa.set_pqg(key.p, key.q, key.g) + dsa.set_key(key.pub_key, nil) + dsa + when OpenSSL::PKey::DH + dh = OpenSSL::PKey::DH.new + dh.set_pqg(key.p, nil, key.g) + dh + else + if defined?(OpenSSL::PKey::EC) && OpenSSL::PKey::EC === key + ec = OpenSSL::PKey::EC.new(key.group) + ec.public_key = key.public_key + ec else - if defined?(OpenSSL::PKey::EC) && OpenSSL::PKey::EC === key - ec = OpenSSL::PKey::EC.new(key.group) - ec.public_key = key.public_key - ec - else - raise "unknown key type" - end + raise "unknown key type" end end end end + +end diff --git a/tool/ruby-openssl-docker/Dockerfile b/tool/ruby-openssl-docker/Dockerfile index 0bafbaae..b8ed4bca 100644 --- a/tool/ruby-openssl-docker/Dockerfile +++ b/tool/ruby-openssl-docker/Dockerfile @@ -1,22 +1,16 @@ -FROM ubuntu:14.04 +FROM ubuntu:16.04 RUN apt-get update && apt-get install -y --no-install-recommends \ autoconf \ bison \ build-essential \ - bzip2 \ ca-certificates \ - cpio \ curl \ - file \ - git \ gzip \ libreadline-dev \ - make \ patch \ pkg-config \ sed \ - xz-utils \ zlib1g-dev # Supported OpenSSL versions: 1.0.1- @@ -35,15 +29,15 @@ RUN curl -s https://www.openssl.org/source/openssl-1.0.1u.tar.gz | tar -C /build shared linux-x86_64 && \ make && make install_sw -RUN curl -s https://www.openssl.org/source/openssl-1.0.2k.tar.gz | tar -C /build/openssl -xzf - && \ - cd /build/openssl/openssl-1.0.2k && \ +RUN curl -s https://www.openssl.org/source/openssl-1.0.2l.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/openssl-1.0.2l && \ ./Configure \ --openssldir=/opt/openssl/openssl-1.0.2 \ shared linux-x86_64 && \ make && make install_sw -RUN curl -s https://www.openssl.org/source/openssl-1.1.0e.tar.gz | tar -C /build/openssl -xzf - && \ - cd /build/openssl/openssl-1.1.0e && \ +RUN curl -s https://www.openssl.org/source/openssl-1.1.0f.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/openssl-1.1.0f && \ ./Configure \ --prefix=/opt/openssl/openssl-1.1.0 \ enable-crypto-mdebug enable-crypto-mdebug-backtrace \ @@ -51,36 +45,42 @@ RUN curl -s https://www.openssl.org/source/openssl-1.1.0e.tar.gz | tar -C /build make && make install_sw # Supported libressl versions: 2.3- -RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.3.10.tar.gz | tar -C /build/openssl -xzf - -RUN cd /build/openssl/libressl-2.3.10 && \ +RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.3.10.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/libressl-2.3.10 && \ ./configure \ --prefix=/opt/openssl/libressl-2.3 && \ make && make install -RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.4.5.tar.gz | tar -C /build/openssl -xzf - -RUN cd /build/openssl/libressl-2.4.5 && \ +RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.4.5.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/libressl-2.4.5 && \ ./configure \ --prefix=/opt/openssl/libressl-2.4 && \ make && make install -RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.5.4.tar.gz | tar -C /build/openssl -xzf - -RUN cd /build/openssl/libressl-2.5.4 && \ +RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.5.5.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/libressl-2.5.5 && \ ./configure \ --prefix=/opt/openssl/libressl-2.5 && \ make && make install +RUN curl -s http://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.6.1.tar.gz | tar -C /build/openssl -xzf - && \ + cd /build/openssl/libressl-2.6.1 && \ + ./configure \ + --prefix=/opt/openssl/libressl-2.6 && \ + make && make install + # Supported Ruby versions: 2.3- RUN mkdir -p /build/ruby -RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.4.tar.gz | tar -C /build/ruby -xzf - && \ - cd /build/ruby/ruby-2.3.4 && \ +RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.5.tar.gz | tar -C /build/ruby -xzf - && \ + cd /build/ruby/ruby-2.3.5 && \ autoconf && ./configure \ --without-openssl \ --prefix=/opt/ruby/ruby-2.3 \ --disable-install-doc && \ make && make install -RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.gz | tar -C /build/ruby -xzf - && \ - cd /build/ruby/ruby-2.4.1 && \ +RUN curl -s https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.2.tar.gz | tar -C /build/ruby -xzf - && \ + cd /build/ruby/ruby-2.4.2 && \ autoconf && ./configure \ --without-openssl \ --prefix=/opt/ruby/ruby-2.4 \ |