diff options
Diffstat (limited to 'ext/openssl/ossl.c')
| -rw-r--r-- | ext/openssl/ossl.c | 941 |
1 files changed, 424 insertions, 517 deletions
diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 93ecc7d414..5716e6f100 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -5,87 +5,83 @@ */ /* * This program is licensed under the same licence as Ruby. - * (See the file 'LICENCE'.) + * (See the file 'COPYING'.) */ #include "ossl.h" #include <stdarg.h> /* for ossl_raise */ -#include <ruby/thread_native.h> /* for OpenSSL < 1.1.0 locks */ /* * Data Conversion */ -#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ -STACK_OF(type) * \ -ossl_##name##_ary2sk0(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - VALUE val; \ - type *x; \ - int i; \ - \ - Check_Type(ary, T_ARRAY); \ - sk = sk_##type##_new_null(); \ - if (!sk) ossl_raise(eOSSLError, NULL); \ - \ - for (i = 0; i < RARRAY_LEN(ary); i++) { \ - val = rb_ary_entry(ary, i); \ - if (!rb_obj_is_kind_of(val, expected_class)) { \ - sk_##type##_pop_free(sk, type##_free); \ - ossl_raise(eOSSLError, "object in array not" \ - " of class ##type##"); \ - } \ - x = dup(val); /* NEED TO DUP */ \ - sk_##type##_push(sk, x); \ - } \ - return sk; \ -} \ - \ -STACK_OF(type) * \ -ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ -{ \ - return (STACK_OF(type)*)rb_protect( \ - (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ - ary, \ - status); \ -} \ - \ -STACK_OF(type) * \ -ossl_##name##_ary2sk(VALUE ary) \ -{ \ - STACK_OF(type) *sk; \ - int status = 0; \ - \ - sk = ossl_protect_##name##_ary2sk(ary, &status); \ - if (status) rb_jump_tag(status); \ - \ - return sk; \ +#define OSSL_IMPL_ARY2SK(name, type, expected_class, dup) \ +VALUE \ +ossl_##name##_ary2sk0(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + VALUE val; \ + type *x; \ + int i; \ + \ + Check_Type(ary, T_ARRAY); \ + sk = sk_##type##_new_null(); \ + if (!sk) ossl_raise(eOSSLError, NULL); \ + \ + for (i = 0; i < RARRAY_LEN(ary); i++) { \ + val = rb_ary_entry(ary, i); \ + if (!rb_obj_is_kind_of(val, expected_class)) { \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, "object in array not" \ + " of class ##type##"); \ + } \ + x = dup(val); /* NEED TO DUP */ \ + if (!sk_##type##_push(sk, x)) { \ + type##_free(x); \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, NULL); \ + } \ + } \ + return (VALUE)sk; \ +} \ + \ +STACK_OF(type) * \ +ossl_protect_##name##_ary2sk(VALUE ary, int *status) \ +{ \ + return (STACK_OF(type)*)rb_protect( \ + (VALUE (*)(VALUE))ossl_##name##_ary2sk0, \ + ary, \ + status); \ +} \ + \ +STACK_OF(type) * \ +ossl_##name##_ary2sk(VALUE ary) \ +{ \ + STACK_OF(type) *sk; \ + int status = 0; \ + \ + sk = ossl_protect_##name##_ary2sk(ary, &status); \ + if (status) rb_jump_tag(status); \ + \ + return sk; \ } OSSL_IMPL_ARY2SK(x509, X509, cX509Cert, DupX509CertPtr) -#define OSSL_IMPL_SK2ARY(name, type) \ -VALUE \ -ossl_##name##_sk2ary(const STACK_OF(type) *sk) \ -{ \ - type *t; \ - int i, num; \ - VALUE ary; \ - \ - if (!sk) { \ - OSSL_Debug("empty sk!"); \ - return Qnil; \ - } \ - num = sk_##type##_num(sk); \ - if (num < 0) { \ - OSSL_Debug("items in sk < -1???"); \ - return rb_ary_new(); \ - } \ - ary = rb_ary_new2(num); \ - \ - for (i=0; i<num; i++) { \ - t = sk_##type##_value(sk, i); \ - rb_ary_push(ary, ossl_##name##_new(t)); \ - } \ - return ary; \ +#define OSSL_IMPL_SK2ARY(name, type) \ +VALUE \ +ossl_##name##_sk2ary(const STACK_OF(type) *sk) \ +{ \ + type *t; \ + int i, num; \ + VALUE ary; \ + \ + RUBY_ASSERT(sk != NULL); \ + num = sk_##type##_num(sk); \ + ary = rb_ary_new_capa(num); \ + \ + for (i=0; i<num; i++) { \ + t = sk_##type##_value(sk, i); \ + rb_ary_push(ary, ossl_##name##_new(t)); \ + } \ + return ary; \ } OSSL_IMPL_SK2ARY(x509, X509) OSSL_IMPL_SK2ARY(x509crl, X509_CRL) @@ -105,14 +101,14 @@ ossl_str_new(const char *ptr, long len, int *pstate) str = rb_protect(ossl_str_new_i, len, &state); if (pstate) - *pstate = state; + *pstate = state; if (state) { - if (!pstate) - rb_set_errinfo(Qnil); - return Qnil; + if (!pstate) + rb_set_errinfo(Qnil); + return Qnil; } if (ptr) - memcpy(RSTRING_PTR(str), ptr, len); + memcpy(RSTRING_PTR(str), ptr, len); return str; } @@ -125,22 +121,22 @@ ossl_buf2str(char *buf, int len) str = ossl_str_new(buf, len, &state); OPENSSL_free(buf); if (state) - rb_jump_tag(state); + rb_jump_tag(state); return str; } void -ossl_bin2hex(unsigned char *in, char *out, size_t inlen) +ossl_bin2hex(const unsigned char *in, char *out, size_t inlen) { const char *hex = "0123456789abcdef"; size_t i; assert(inlen <= LONG_MAX / 2); for (i = 0; i < inlen; i++) { - unsigned char p = in[i]; + unsigned char p = in[i]; - out[i * 2 + 0] = hex[p >> 4]; - out[i * 2 + 1] = hex[p & 0x0f]; + out[i * 2 + 0] = hex[p >> 4]; + out[i * 2 + 1] = hex[p & 0x0f]; } } @@ -151,14 +147,14 @@ VALUE ossl_pem_passwd_value(VALUE pass) { if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); /* PEM_BUFSIZE is currently used as the second argument of pem_password_cb, * that is +max_len+ of ossl_pem_passwd_cb() */ if (RSTRING_LEN(pass) > PEM_BUFSIZE) - ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); + ossl_raise(eOSSLError, "password must not be longer than %d bytes", PEM_BUFSIZE); return pass; } @@ -168,7 +164,7 @@ ossl_pem_passwd_cb0(VALUE flag) { VALUE pass = rb_yield(flag); if (NIL_P(pass)) - return Qnil; + return Qnil; StringValue(pass); return pass; } @@ -181,46 +177,46 @@ ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_) VALUE rflag, pass = (VALUE)pwd_; if (RTEST(pass)) { - /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not - * work because it does not allow NUL characters and truncates to 1024 - * bytes silently if the input is over 1024 bytes */ - if (RB_TYPE_P(pass, T_STRING)) { - len = RSTRING_LEN(pass); - if (len <= max_len) { - memcpy(buf, RSTRING_PTR(pass), len); - return (int)len; - } - } - OSSL_Debug("passed data is not valid String???"); - return -1; + /* PEM_def_callback(buf, max_len, flag, StringValueCStr(pass)) does not + * work because it does not allow NUL characters and truncates to 1024 + * bytes silently if the input is over 1024 bytes */ + if (RB_TYPE_P(pass, T_STRING)) { + len = RSTRING_LEN(pass); + if (len <= max_len) { + memcpy(buf, RSTRING_PTR(pass), len); + return (int)len; + } + } + OSSL_Debug("passed data is not valid String???"); + return -1; } if (!rb_block_given_p()) { - return PEM_def_callback(buf, max_len, flag, NULL); + return PEM_def_callback(buf, max_len, flag, NULL); } while (1) { - /* - * when the flag is nonzero, this passphrase - * will be used to perform encryption; otherwise it will - * be used to perform decryption. - */ - rflag = flag ? Qtrue : Qfalse; - pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); - if (status) { - /* ignore an exception raised. */ - rb_set_errinfo(Qnil); - return -1; - } - if (NIL_P(pass)) - return -1; - len = RSTRING_LEN(pass); - if (len > max_len) { - rb_warning("password must not be longer than %d bytes", max_len); - continue; - } - memcpy(buf, RSTRING_PTR(pass), len); - break; + /* + * when the flag is nonzero, this password + * will be used to perform encryption; otherwise it will + * be used to perform decryption. + */ + rflag = flag ? Qtrue : Qfalse; + pass = rb_protect(ossl_pem_passwd_cb0, rflag, &status); + if (status) { + /* ignore an exception raised. */ + rb_set_errinfo(Qnil); + return -1; + } + if (NIL_P(pass)) + return -1; + len = RSTRING_LEN(pass); + if (len > max_len) { + rb_warning("password must not be longer than %d bytes", max_len); + continue; + } + memcpy(buf, RSTRING_PTR(pass), len); + break; } return (int)len; } @@ -255,38 +251,46 @@ VALUE ossl_to_der_if_possible(VALUE obj) { if(rb_respond_to(obj, ossl_s_to_der)) - return ossl_to_der(obj); + return ossl_to_der(obj); return obj; } /* * Errors */ -static VALUE -ossl_make_error(VALUE exc, const char *fmt, va_list args) +static ID id_i_errors; + +static void collect_errors_into(VALUE ary); + +VALUE +ossl_make_error(VALUE exc, VALUE str) { - VALUE str = Qnil; unsigned long e; + const char *data; + int flags; + VALUE errors = rb_ary_new(); - if (fmt) { - str = rb_vsprintf(fmt, args); - } - e = ERR_peek_last_error(); + if (NIL_P(str)) + str = rb_str_new(NULL, 0); + +#ifdef HAVE_ERR_GET_ERROR_ALL + e = ERR_peek_last_error_all(NULL, NULL, NULL, &data, &flags); +#else + e = ERR_peek_last_error_line_data(NULL, NULL, &data, &flags); +#endif if (e) { - const char *msg = ERR_reason_error_string(e); - - if (NIL_P(str)) { - if (msg) str = rb_str_new_cstr(msg); - } - else { - if (RSTRING_LEN(str)) rb_str_cat2(str, ": "); - rb_str_cat2(str, msg ? msg : "(null)"); - } - ossl_clear_error(); + const char *msg = ERR_reason_error_string(e); + + if (RSTRING_LEN(str)) rb_str_cat_cstr(str, ": "); + rb_str_cat_cstr(str, msg ? msg : "(null)"); + if (flags & ERR_TXT_STRING && data) + rb_str_catf(str, " (%s)", data); + collect_errors_into(errors); } - if (NIL_P(str)) str = rb_str_new(0, 0); - return rb_exc_new3(exc, str); + VALUE obj = rb_exc_new_str(exc, str); + rb_ivar_set(obj, id_i_errors, errors); + return obj; } void @@ -294,51 +298,111 @@ ossl_raise(VALUE exc, const char *fmt, ...) { va_list args; VALUE err; - va_start(args, fmt); - err = ossl_make_error(exc, fmt, args); - va_end(args); - rb_exc_raise(err); + + if (fmt) { + va_start(args, fmt); + err = rb_vsprintf(fmt, args); + va_end(args); + } + else { + err = Qnil; + } + + rb_exc_raise(ossl_make_error(exc, err)); } -void -ossl_clear_error(void) +static void +collect_errors_into(VALUE ary) { - if (dOSSL == Qtrue) { - unsigned long e; - const char *file, *data, *errstr; - int line, flags; - - while ((e = ERR_get_error_line_data(&file, &line, &data, &flags))) { - errstr = ERR_error_string(e, NULL); - if (!errstr) - errstr = "(null)"; - - if (flags & ERR_TXT_STRING) { - if (!data) - data = "(null)"; - rb_warn("error on stack: %s (%s)", errstr, data); - } - else { - rb_warn("error on stack: %s", errstr); - } - } + if (dOSSL == Qtrue || !NIL_P(ary)) { + unsigned long e; + const char *file, *data, *func, *lib, *reason; + int line, flags; + +#ifdef HAVE_ERR_GET_ERROR_ALL + while ((e = ERR_get_error_all(&file, &line, &func, &data, &flags))) { +#else + while ((e = ERR_get_error_line_data(&file, &line, &data, &flags))) { + func = ERR_func_error_string(e); +#endif + lib = ERR_lib_error_string(e); + reason = ERR_reason_error_string(e); + + VALUE str = rb_sprintf("error:%08lX:%s:%s:%s", e, lib ? lib : "", + func ? func : "", reason ? reason : ""); + if (flags & ERR_TXT_STRING) { + if (!data) + data = "(null)"; + rb_str_catf(str, " (%s)", data); + } + + if (dOSSL == Qtrue) + rb_warn("error on stack: %"PRIsVALUE, str); + if (!NIL_P(ary)) + rb_ary_push(ary, str); + } } else { - ERR_clear_error(); + ERR_clear_error(); + } +} + +void +ossl_clear_error(void) +{ + collect_errors_into(Qnil); +} + +/* + * call-seq: + * ossl_error.detailed_message(**) -> string + * + * Returns the exception message decorated with the captured \OpenSSL error + * queue entries. + */ +static VALUE +osslerror_detailed_message(int argc, VALUE *argv, VALUE self) +{ + VALUE str; +#ifdef HAVE_RB_CALL_SUPER_KW + // Ruby >= 3.2 + if (RTEST(rb_funcall(rb_eException, rb_intern("method_defined?"), 1, + ID2SYM(rb_intern("detailed_message"))))) + str = rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS); + else +#endif + str = rb_funcall(self, rb_intern("message"), 0); + VALUE errors = rb_attr_get(self, id_i_errors); + + // OpenSSLError was not created by ossl_make_error() + if (!RB_TYPE_P(errors, T_ARRAY)) + return str; + + str = rb_str_resurrect(str); + rb_str_catf(str, "\nOpenSSL error queue reported %ld errors:", + RARRAY_LEN(errors)); + for (long i = 0; i < RARRAY_LEN(errors); i++) { + VALUE err = RARRAY_AREF(errors, i); + rb_str_catf(str, "\n%"PRIsVALUE, err); } + return str; } /* * call-seq: * OpenSSL.errors -> [String...] * - * See any remaining errors held in queue. + * Returns any remaining errors held in the \OpenSSL thread-local error queue + * and clears the queue. This should normally return an empty array. + * + * This is intended for debugging Ruby/OpenSSL. If you see any errors here, + * it likely indicates a bug in the extension. Please file an issue at + * https://github.com/ruby/openssl. * - * Any errors you see here are probably due to a bug in Ruby's OpenSSL - * implementation. + * For debugging your program, OpenSSL.debug= may be useful. */ -VALUE -ossl_get_errors(void) +static VALUE +ossl_get_errors(VALUE _) { VALUE ary; long e; @@ -356,25 +420,11 @@ ossl_get_errors(void) */ VALUE dOSSL; -#if !defined(HAVE_VA_ARGS_MACRO) -void -ossl_debug(const char *fmt, ...) -{ - va_list args; - - if (dOSSL == Qtrue) { - fprintf(stderr, "OSSL_DEBUG: "); - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - fprintf(stderr, " [CONTEXT N/A]\n"); - } -} -#endif - /* * call-seq: * OpenSSL.debug -> true | false + * + * Returns whether Ruby/OpenSSL's debug mode is currently enabled. */ static VALUE ossl_debug_get(VALUE self) @@ -384,9 +434,9 @@ ossl_debug_get(VALUE self) /* * call-seq: - * OpenSSL.debug = boolean -> boolean + * OpenSSL.debug = boolean * - * Turns on or off debug mode. With debug mode, all erros added to the OpenSSL + * Turns on or off debug mode. With debug mode, all errors added to the \OpenSSL * error queue will be printed to stderr. */ static VALUE @@ -398,14 +448,20 @@ ossl_debug_set(VALUE self, VALUE val) } /* - * call-seq + * call-seq: * OpenSSL.fips_mode -> true | false + * + * Returns whether the FIPS mode is currently enabled. */ static VALUE ossl_fips_mode_get(VALUE self) { -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + VALUE enabled; + enabled = EVP_default_properties_is_fips_enabled(NULL) ? Qtrue : Qfalse; + return enabled; +#elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) VALUE enabled; enabled = FIPS_mode() ? Qtrue : Qfalse; return enabled; @@ -416,10 +472,10 @@ ossl_fips_mode_get(VALUE self) /* * call-seq: - * OpenSSL.fips_mode = boolean -> boolean + * OpenSSL.fips_mode = boolean * * Turns FIPS mode on or off. Turning on FIPS mode will obviously only have an - * effect for FIPS-capable installations of the OpenSSL library. Trying to do + * effect for FIPS-capable installations of the \OpenSSL library. Trying to do * so otherwise will result in an error. * * === Examples @@ -429,183 +485,73 @@ ossl_fips_mode_get(VALUE self) static VALUE ossl_fips_mode_set(VALUE self, VALUE enabled) { - -#ifdef OPENSSL_FIPS +#if OSSL_OPENSSL_PREREQ(3, 0, 0) if (RTEST(enabled)) { - int mode = FIPS_mode(); - if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ - ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + if (!EVP_default_properties_enable_fips(NULL, 1)) { + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } } else { - if(!FIPS_mode_set(0)) /* turning off twice is OK */ - ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + if (!EVP_default_properties_enable_fips(NULL, 0)) { + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); + } + } + return enabled; +#elif defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) + if (RTEST(enabled)) { + int mode = FIPS_mode(); + if(!mode && !FIPS_mode_set(1)) /* turning on twice leads to an error */ + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } else { + if(!FIPS_mode_set(0)) /* turning off twice is OK */ + ossl_raise(eOSSLError, "Turning off FIPS mode failed"); } return enabled; #else if (RTEST(enabled)) - ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); + ossl_raise(eOSSLError, "This version of OpenSSL does not support FIPS mode"); return enabled; #endif } -#if defined(OSSL_DEBUG) -#if !defined(LIBRESSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(OPENSSL_NO_CRYPTO_MDEBUG) || \ - defined(CRYPTO_malloc_debug_init)) /* * call-seq: - * OpenSSL.mem_check_start -> nil + * OpenSSL.fixed_length_secure_compare(string, string) -> true or false * - * Calls CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON). Starts tracking memory - * allocations. See also OpenSSL.print_mem_leaks. + * Constant time memory comparison for fixed length strings, such as results + * of \HMAC calculations. * - * This is available only when built with a capable OpenSSL and --enable-debug - * configure option. + * Returns +true+ if the strings are identical, +false+ if they are of the same + * length but not identical. If the length is different, ArgumentError is + * raised. */ static VALUE -mem_check_start(VALUE self) +ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) { - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); - return Qnil; -} + const unsigned char *p1; + const unsigned char *p2; + long len1; + long len2; -/* - * call-seq: - * OpenSSL.print_mem_leaks -> true | false - * - * For debugging the Ruby/OpenSSL library. Calls CRYPTO_mem_leaks_fp(stderr). - * Prints detected memory leaks to standard error. This cleans the global state - * up thus you cannot use any methods of the library after calling this. - * - * Returns +true+ if leaks detected, +false+ otherwise. - * - * This is available only when built with a capable OpenSSL and --enable-debug - * configure option. - * - * === Example - * OpenSSL.mem_check_start - * NOT_GCED = OpenSSL::PKey::RSA.new(256) - * - * END { - * GC.start - * OpenSSL.print_mem_leaks # will print the leakage - * } - */ -static VALUE -print_mem_leaks(VALUE self) -{ -#if OPENSSL_VERSION_NUMBER >= 0x10100000 - int ret; -#endif - - BN_CTX_free(ossl_bn_ctx); - ossl_bn_ctx = NULL; - -#if OPENSSL_VERSION_NUMBER >= 0x10100000 - ret = CRYPTO_mem_leaks_fp(stderr); - if (ret < 0) - ossl_raise(eOSSLError, "CRYPTO_mem_leaks_fp"); - return ret ? Qfalse : Qtrue; -#else - CRYPTO_mem_leaks_fp(stderr); - return Qnil; -#endif -} -#endif -#endif - -#if !defined(HAVE_OPENSSL_110_THREADING_API) -/** - * Stores locks needed for OpenSSL thread safety - */ -struct CRYPTO_dynlock_value { - rb_nativethread_lock_t lock; - rb_nativethread_id_t owner; - size_t count; -}; + StringValue(str1); + StringValue(str2); -static void -ossl_lock_init(struct CRYPTO_dynlock_value *l) -{ - rb_nativethread_lock_initialize(&l->lock); - l->count = 0; -} + p1 = (const unsigned char *)RSTRING_PTR(str1); + p2 = (const unsigned char *)RSTRING_PTR(str2); + len1 = RSTRING_LEN(str1); + len2 = RSTRING_LEN(str2); -static void -ossl_lock_unlock(int mode, struct CRYPTO_dynlock_value *l) -{ - 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); + if (len1 != len2) { + ossl_raise(rb_eArgError, "inputs must be of equal length"); } -} - -static struct CRYPTO_dynlock_value * -ossl_dyn_create_callback(const char *file, int line) -{ - /* 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); -} - -static void -ossl_dyn_destroy_callback(struct CRYPTO_dynlock_value *l, const char *file, int line) -{ - rb_nativethread_lock_destroy(&l->lock); - OPENSSL_free(l); -} - -static void ossl_threadid_func(CRYPTO_THREADID *id) -{ - /* register native thread 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(); - - 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); - CRYPTO_set_dynlock_create_callback(ossl_dyn_create_callback); - CRYPTO_set_dynlock_lock_callback(ossl_dyn_lock_callback); - CRYPTO_set_dynlock_destroy_callback(ossl_dyn_destroy_callback); + switch (CRYPTO_memcmp(p1, p2, len1)) { + case 0: return Qtrue; + default: return Qfalse; + } } -#endif /* !HAVE_OPENSSL_110_THREADING_API */ /* - * OpenSSL provides SSL, TLS and general purpose cryptography. It wraps the + * OpenSSL provides \SSL, TLS and general purpose cryptography. It wraps the * OpenSSL[https://www.openssl.org/] library. * * = Examples @@ -626,23 +572,21 @@ static void Init_ossl_locks(void) * * key = OpenSSL::PKey::RSA.new 2048 * - * open 'private_key.pem', 'w' do |io| io.write key.to_pem end - * open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end + * File.write 'private_key.pem', key.private_to_pem + * File.write 'public_key.pem', key.public_to_pem * * === Exporting a Key * * Keys saved to disk without encryption are not secure as anyone who gets * ahold of the key may use it unless it is encrypted. In order to securely - * export a key you may export it with a pass phrase. + * export a key you may export it with a password. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' - * pass_phrase = 'my secure pass phrase goes here' + * cipher = OpenSSL::Cipher.new 'aes-256-cbc' + * password = 'my secure password goes here' * - * key_secure = key.export cipher, pass_phrase + * key_secure = key.private_to_pem cipher, password * - * open 'private.secure.pem', 'w' do |io| - * io.write key_secure - * end + * File.write 'private.secure.pem', key_secure * * OpenSSL::Cipher.ciphers returns a list of available ciphers. * @@ -650,25 +594,25 @@ static void Init_ossl_locks(void) * * A key can also be loaded from a file. * - * key2 = OpenSSL::PKey::RSA.new File.read 'private_key.pem' + * key2 = OpenSSL::PKey.read File.read 'private_key.pem' * key2.public? # => true * key2.private? # => true * * or * - * key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem' + * key3 = OpenSSL::PKey.read File.read 'public_key.pem' * key3.public? # => true * key3.private? # => false * * === Loading an Encrypted Key * - * OpenSSL will prompt you for your pass phrase when loading an encrypted key. - * If you will not be able to type in the pass phrase you may provide it when + * \OpenSSL will prompt you for your password when loading an encrypted key. + * If you will not be able to type in the password you may provide it when * loading the key: * * key4_pem = File.read 'private.secure.pem' - * pass_phrase = 'my secure pass phrase goes here' - * key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase + * password = 'my secure password goes here' + * key4 = OpenSSL::PKey.read key4_pem, password * * == RSA Encryption * @@ -710,16 +654,14 @@ static void Init_ossl_locks(void) * To sign a document, a cryptographically secure hash of the document is * computed first, which is then signed using the private key. * - * digest = OpenSSL::Digest::SHA256.new - * signature = key.sign digest, document + * signature = key.sign 'SHA256', document * * To validate the signature, again a hash of the document is computed and * the signature is decrypted using the public key. The result is then * compared to the hash just computed, if they are equal the signature was * valid. * - * digest = OpenSSL::Digest::SHA256.new - * if key.verify digest, signature, document + * if key.verify 'SHA256', signature, document * puts 'Valid' * else * puts 'Invalid' @@ -727,7 +669,7 @@ static void Init_ossl_locks(void) * * == PBKDF2 Password-based Encryption * - * If supported by the underlying OpenSSL version used, Password-based + * If supported by the underlying \OpenSSL version used, Password-based * Encryption should use the features of PKCS5. If not supported or if * required by legacy applications, the older, less secure methods specified * in RFC 2898 are also supported (see below). @@ -745,7 +687,7 @@ static void Init_ossl_locks(void) * using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt, * the number of iterations largely depends on the hardware being used. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'aes-256-cbc' * cipher.encrypt * iv = cipher.random_iv * @@ -753,7 +695,7 @@ static void Init_ossl_locks(void) * salt = OpenSSL::Random.random_bytes 16 * iter = 20000 * key_len = cipher.key_len - * digest = OpenSSL::Digest::SHA256.new + * digest = OpenSSL::Digest.new('SHA256') * * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) * cipher.key = key @@ -768,7 +710,7 @@ static void Init_ossl_locks(void) * Use the same steps as before to derive the symmetric AES key, this time * setting the Cipher up for decryption. * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = OpenSSL::Cipher.new 'aes-256-cbc' * cipher.decrypt * cipher.iv = iv # the one generated with #random_iv * @@ -776,7 +718,7 @@ static void Init_ossl_locks(void) * salt = ... # the one generated above * iter = 20000 * key_len = cipher.key_len - * digest = OpenSSL::Digest::SHA256.new + * digest = OpenSSL::Digest.new('SHA256') * * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) * cipher.key = key @@ -786,46 +728,7 @@ static void Init_ossl_locks(void) * decrypted = cipher.update encrypted * decrypted << cipher.final * - * == PKCS #5 Password-based Encryption - * - * PKCS #5 is a password-based encryption standard documented at - * RFC2898[http://www.ietf.org/rfc/rfc2898.txt]. It allows a short password or - * passphrase to be used to create a secure encryption key. If possible, PBKDF2 - * as described above should be used if the circumstances allow it. - * - * PKCS #5 uses a Cipher, a pass phrase and a salt to generate an encryption - * key. - * - * pass_phrase = 'my secure pass phrase goes here' - * salt = '8 octets' - * - * === Encryption - * - * First set up the cipher for encryption - * - * encryptor = OpenSSL::Cipher.new 'AES-128-CBC' - * encryptor.encrypt - * encryptor.pkcs5_keyivgen pass_phrase, salt - * - * Then pass the data you want to encrypt through - * - * encrypted = encryptor.update 'top secret document' - * encrypted << encryptor.final - * - * === Decryption - * - * Use a new Cipher instance set up for decryption - * - * decryptor = OpenSSL::Cipher.new 'AES-128-CBC' - * decryptor.decrypt - * decryptor.pkcs5_keyivgen pass_phrase, salt - * - * Then pass the data you want to decrypt through - * - * plain = decryptor.update encrypted - * plain << decryptor.final - * - * == X509 Certificates + * == \X509 Certificates * * === Creating a Certificate * @@ -833,7 +736,7 @@ static void Init_ossl_locks(void) * signature. * * key = OpenSSL::PKey::RSA.new 2048 - * name = OpenSSL::X509::Name.parse 'CN=nobody/DC=example' + * name = OpenSSL::X509::Name.parse '/CN=nobody/DC=example' * * cert = OpenSSL::X509::Certificate.new * cert.version = 2 @@ -862,7 +765,7 @@ static void Init_ossl_locks(void) * extension_factory.create_extension('subjectKeyIdentifier', 'hash') * * The list of supported extensions (and in some cases their possible values) - * can be derived from the "objects.h" file in the OpenSSL source code. + * can be derived from the "objects.h" file in the \OpenSSL source code. * * === Signing a Certificate * @@ -872,7 +775,7 @@ static void Init_ossl_locks(void) * certificate. * * cert.issuer = name - * cert.sign key, OpenSSL::Digest::SHA1.new + * cert.sign key, OpenSSL::Digest.new('SHA1') * * open 'certificate.pem', 'w' do |io| io.write cert.to_pem end * @@ -902,12 +805,12 @@ static void Init_ossl_locks(void) * not readable by other users. * * ca_key = OpenSSL::PKey::RSA.new 2048 - * pass_phrase = 'my secure pass phrase goes here' + * password = 'my secure password goes here' * - * cipher = OpenSSL::Cipher.new 'AES-128-CBC' + * cipher = 'aes-256-cbc' * * open 'ca_key.pem', 'w', 0400 do |io| - * io.write ca_key.export(cipher, pass_phrase) + * io.write ca_key.private_to_pem(cipher, password) * end * * === CA Certificate @@ -915,7 +818,7 @@ static void Init_ossl_locks(void) * A CA certificate is created the same way we created a certificate above, but * with different extensions. * - * ca_name = OpenSSL::X509::Name.parse 'CN=ca/DC=example' + * ca_name = OpenSSL::X509::Name.parse '/CN=ca/DC=example' * * ca_cert = OpenSSL::X509::Certificate.new * ca_cert.serial = 0 @@ -948,7 +851,7 @@ static void Init_ossl_locks(void) * * Root CA certificates are self-signed. * - * ca_cert.sign ca_key, OpenSSL::Digest::SHA1.new + * ca_cert.sign ca_key, OpenSSL::Digest.new('SHA1') * * The CA certificate is saved to disk so it may be distributed to all the * users of the keys this CA will sign. @@ -966,7 +869,7 @@ static void Init_ossl_locks(void) * csr.version = 0 * csr.subject = name * csr.public_key = key.public_key - * csr.sign key, OpenSSL::Digest::SHA1.new + * csr.sign key, OpenSSL::Digest.new('SHA1') * * A CSR is saved to disk and sent to the CA for signing. * @@ -1010,29 +913,29 @@ static void Init_ossl_locks(void) * csr_cert.add_extension \ * extension_factory.create_extension('subjectKeyIdentifier', 'hash') * - * csr_cert.sign ca_key, OpenSSL::Digest::SHA1.new + * csr_cert.sign ca_key, OpenSSL::Digest.new('SHA1') * * open 'csr_cert.pem', 'w' do |io| * io.write csr_cert.to_pem * end * - * == SSL and TLS Connections + * == \SSL and TLS Connections * - * Using our created key and certificate we can create an SSL or TLS connection. - * An SSLContext is used to set up an SSL session. + * Using our created key and certificate we can create an \SSL or TLS + * connection. An OpenSSL::SSL::SSLContext is used to set up an \SSL session. * * context = OpenSSL::SSL::SSLContext.new * - * === SSL Server + * === \SSL Server * - * An SSL server requires the certificate and private key to communicate + * An \SSL server requires the certificate and private key to communicate * securely with its clients: * * context.cert = cert * context.key = key * - * Then create an SSLServer with a TCP server socket and the context. Use the - * SSLServer like an ordinary TCP server. + * Then create an OpenSSL::SSL::SSLServer with a TCP server socket and the + * context. Use the SSLServer like an ordinary TCP server. * * require 'socket' * @@ -1042,23 +945,24 @@ static void Init_ossl_locks(void) * loop do * ssl_connection = ssl_server.accept * - * data = connection.gets + * data = ssl_connection.gets * * response = "I got #{data.dump}" * puts response * - * connection.puts "I got #{data.dump}" - * connection.close + * ssl_connection.puts "I got #{data.dump}" + * ssl_connection.close * end * - * === SSL client + * === \SSL client * - * An SSL client is created with a TCP socket and the context. - * SSLSocket#connect must be called to initiate the SSL handshake and start - * encryption. A key and certificate are not required for the client socket. + * An \SSL client is created with a TCP socket and the context. + * OpenSSL::SSL::SSLSocket#connect must be called to initiate the \SSL handshake + * and start encryption. A key and certificate are not required for the client + * socket. * - * Note that SSLSocket#close doesn't close the underlying socket by default. Set - * SSLSocket#sync_close to true if you want. + * Note that OpenSSL::SSL::SSLSocket#close doesn't close the underlying socket + * by default. Set OpenSSL::SSL::SSLSocket#sync_close to true if you want. * * require 'socket' * @@ -1074,7 +978,7 @@ static void Init_ossl_locks(void) * * === Peer Verification * - * An unverified SSL connection does not provide much security. For enhanced + * An unverified \SSL connection does not provide much security. For enhanced * security the client or server can verify the certificate of its peer. * * The client can be modified to verify the server's certificate against the @@ -1099,6 +1003,11 @@ static void Init_ossl_locks(void) void Init_openssl(void) { +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + +#undef rb_intern /* * Init timezone info */ @@ -1109,74 +1018,109 @@ Init_openssl(void) /* * Init all digests, ciphers */ - /* CRYPTO_malloc_init(); */ - /* ENGINE_load_builtin_engines(); */ - 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 + if (!OPENSSL_init_ssl(0, NULL)) + rb_raise(rb_eRuntimeError, "OPENSSL_init_ssl"); /* * Init main module */ - mOSSL = rb_define_module("OpenSSL"); rb_global_variable(&mOSSL); + mOSSL = rb_define_module("OpenSSL"); + rb_define_singleton_method(mOSSL, "fixed_length_secure_compare", ossl_crypto_fixed_length_secure_compare, 2); /* - * OpenSSL ruby extension version + * \OpenSSL library version string used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. */ - rb_define_const(mOSSL, "VERSION", rb_str_new2(OSSL_VERSION)); + rb_define_const(mOSSL, "OPENSSL_VERSION", + rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); /* - * Version of OpenSSL the ruby OpenSSL extension was built with + * \OpenSSL library version string currently used at runtime. */ - rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); + rb_define_const( + mOSSL, + "OPENSSL_LIBRARY_VERSION", + rb_obj_freeze(rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION))) + ); /* - * Version of OpenSSL the ruby OpenSSL extension is running with + * \OpenSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. + * + * The version number is encoded into a single integer value. The number + * follows the format: + * + * [\OpenSSL 3.0.0 or later] + * <tt>0xMNN00PP0</tt> (major minor 00 patch 0) + * [\OpenSSL 1.1.1 or earlier] + * <tt>0xMNNFFPPS</tt> (major minor fix patch status) + * [LibreSSL] + * <tt>0x20000000</tt> (a fixed value) + * + * See also the man page OPENSSL_VERSION_NUMBER(3). */ - rb_define_const(mOSSL, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION))); + rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); +#if defined(LIBRESSL_VERSION_NUMBER) /* - * Version number of OpenSSL the ruby OpenSSL extension was built with - * (base 16) + * LibreSSL library version number used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. + * + * This constant is only defined if the extension was compiled against + * LibreSSL. The number follows the format: + * <tt>0xMNNFF00f</tt> (major minor fix 00 status). + * + * See also the man page LIBRESSL_VERSION_NUMBER(3). */ - rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); + rb_define_const(mOSSL, "LIBRESSL_VERSION_NUMBER", INT2NUM(LIBRESSL_VERSION_NUMBER)); +#endif /* - * Boolean indicating whether OpenSSL is FIPS-capable or not + * Boolean indicating whether the \OpenSSL library is FIPS-capable or not. + * Always <tt>true</tt> for \OpenSSL 3.0 and later. + * + * This is obsolete and will be removed in the future. + * See also OpenSSL.fips_mode. */ rb_define_const(mOSSL, "OPENSSL_FIPS", -#ifdef OPENSSL_FIPS - Qtrue +/* OpenSSL 3 is FIPS-capable even when it is installed without fips option */ +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + Qtrue +#elif defined(OPENSSL_FIPS) + Qtrue +#elif defined(OPENSSL_IS_AWSLC) // AWS-LC FIPS can only be enabled during compile time. + FIPS_mode() ? Qtrue : Qfalse #else - Qfalse + Qfalse #endif - ); + ); rb_define_module_function(mOSSL, "fips_mode", ossl_fips_mode_get, 0); rb_define_module_function(mOSSL, "fips_mode=", ossl_fips_mode_set, 1); + rb_global_variable(&eOSSLError); /* - * Generic error, - * common for all classes under OpenSSL module + * Generic error class for OpenSSL. All error classes in this library + * inherit from this class. + * + * This class indicates that an error was reported by the underlying + * \OpenSSL library. */ - eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); - rb_global_variable(&eOSSLError); + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); + /* + * \OpenSSL error queue entries captured at the time the exception was + * raised. The same information is printed to stderr if OpenSSL.debug is + * set to +true+. + * + * This is an array of zero or more strings, ordered from the oldest to the + * newest. The format of the strings is not stable and may vary across + * versions of \OpenSSL or versions of this Ruby extension. + * + * See also the man page ERR_get_error(3). + */ + rb_attr(eOSSLError, rb_intern_const("errors"), 1, 0, 0); + rb_define_method(eOSSLError, "detailed_message", osslerror_detailed_message, -1); /* * Init debug core @@ -1192,64 +1136,27 @@ Init_openssl(void) * Get ID of to_der */ ossl_s_to_der = rb_intern("to_der"); - -#if !defined(HAVE_OPENSSL_110_THREADING_API) - Init_ossl_locks(); -#endif + id_i_errors = rb_intern("@errors"); /* * Init components */ + Init_ossl_asn1(); Init_ossl_bn(); Init_ossl_cipher(); Init_ossl_config(); Init_ossl_digest(); + Init_ossl_engine(); Init_ossl_hmac(); + Init_ossl_kdf(); Init_ossl_ns_spki(); + Init_ossl_ocsp(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); Init_ossl_pkey(); + Init_ossl_provider(); Init_ossl_rand(); Init_ossl_ssl(); + Init_ossl_ts(); Init_ossl_x509(); - Init_ossl_ocsp(); - Init_ossl_engine(); - Init_ossl_asn1(); - Init_ossl_kdf(); - -#if defined(OSSL_DEBUG) - /* - * For debugging Ruby/OpenSSL. Enable only when built with --enable-debug - */ -#if !defined(LIBRESSL_VERSION_NUMBER) && \ - (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(OPENSSL_NO_CRYPTO_MDEBUG) || \ - defined(CRYPTO_malloc_debug_init)) - rb_define_module_function(mOSSL, "mem_check_start", mem_check_start, 0); - rb_define_module_function(mOSSL, "print_mem_leaks", print_mem_leaks, 0); - -#if defined(CRYPTO_malloc_debug_init) /* <= 1.0.2 */ - CRYPTO_malloc_debug_init(); -#endif - -#if defined(V_CRYPTO_MDEBUG_ALL) /* <= 1.0.2 */ - CRYPTO_set_mem_debug_options(V_CRYPTO_MDEBUG_ALL); -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10100000 /* <= 1.0.2 */ - { - int i; - /* - * See crypto/ex_data.c; call def_get_class() immediately to avoid - * allocations. 15 is the maximum number that is used as the class index - * in OpenSSL 1.0.2. - */ - for (i = 0; i <= 15; i++) { - if (CRYPTO_get_ex_new_index(i, 0, (void *)"ossl-mdebug-dummy", 0, 0, 0) < 0) - rb_raise(rb_eRuntimeError, "CRYPTO_get_ex_new_index for " - "class index %d failed", i); - } - } -#endif -#endif -#endif } |
