diff options
Diffstat (limited to 'ext/openssl/ossl.c')
| -rw-r--r-- | ext/openssl/ossl.c | 1266 |
1 files changed, 976 insertions, 290 deletions
diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index a98f2641cc..5716e6f100 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1,238 +1,224 @@ /* - * $Id$ * 'OpenSSL for Ruby' project * Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz> * All rights reserved. */ /* - * This program is licenced under the same licence as Ruby. - * (See the file 'LICENCE'.) + * This program is licensed under the same licence as Ruby. + * (See the file 'COPYING'.) */ #include "ossl.h" #include <stdarg.h> /* for ossl_raise */ /* - * String to HEXString conversion - */ -int -string2hex(char *buf, int buf_len, char **hexbuf, int *hexbuf_len) -{ - static const char hex[]="0123456789abcdef"; - int i, len = 2 * buf_len; - - if (buf_len < 0 || len < buf_len) { /* PARANOIA? */ - return -1; - } - if (!hexbuf) { /* if no buf, return calculated len */ - if (hexbuf_len) { - *hexbuf_len = len; - } - return len; - } - if (!(*hexbuf = OPENSSL_malloc(len + 1))) { - return -1; - } - for (i = 0; i < buf_len; i++) { - (*hexbuf)[2 * i] = hex[((unsigned char)buf[i]) >> 4]; - (*hexbuf)[2 * i + 1] = hex[buf[i] & 0x0f]; - } - (*hexbuf)[2 * i] = '\0'; - - if (hexbuf_len) { - *hexbuf_len = len; - } - return len; -} - -/* * Data Conversion */ -STACK_OF(X509) * -ossl_x509_ary2sk0(VALUE ary) -{ - STACK_OF(X509) *sk; - VALUE val; - X509 *x509; - int i; - - Check_Type(ary, T_ARRAY); - sk = sk_X509_new_null(); - if (!sk) ossl_raise(eOSSLError, NULL); - - for (i = 0; i < RARRAY(ary)->len; i++) { - val = rb_ary_entry(ary, i); - if (!rb_obj_is_kind_of(val, cX509Cert)) { - sk_X509_pop_free(sk, X509_free); - ossl_raise(eOSSLError, "object not X509 cert in array"); - } - x509 = DupX509CertPtr(val); /* NEED TO DUP */ - sk_X509_push(sk, x509); - } - return sk; -} - -STACK_OF(X509) * -ossl_protect_x509_ary2sk(VALUE ary, int *status) -{ - return (STACK_OF(X509)*)rb_protect((VALUE(*)_((VALUE)))ossl_x509_ary2sk0, - ary, status); -} - -STACK_OF(X509) * -ossl_x509_ary2sk(VALUE ary) -{ - STACK_OF(X509) *sk; - int status = 0; - - sk = ossl_protect_x509_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(STACK *sk) \ -{ \ - type *t; \ - int i, num; \ - VALUE ary; \ - \ - if (!sk) { \ - OSSL_Debug("empty sk!"); \ - return Qnil; \ - } \ - num = sk_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 = (type *)sk_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) +OSSL_IMPL_SK2ARY(x509name, X509_NAME) static VALUE -ossl_str_new(int size) +ossl_str_new_i(VALUE size) { - return rb_str_new(0, size); + return rb_str_new(NULL, (long)size); +} + +VALUE +ossl_str_new(const char *ptr, long len, int *pstate) +{ + VALUE str; + int state; + + str = rb_protect(ossl_str_new_i, len, &state); + if (pstate) + *pstate = state; + if (state) { + if (!pstate) + rb_set_errinfo(Qnil); + return Qnil; + } + if (ptr) + memcpy(RSTRING_PTR(str), ptr, len); + return str; } VALUE ossl_buf2str(char *buf, int len) { VALUE str; - int status = 0; + int state; - str = rb_protect((VALUE(*)_((VALUE)))ossl_str_new, len, &status); - if(!NIL_P(str)) memcpy(RSTRING(str)->ptr, buf, len); + str = ossl_str_new(buf, len, &state); OPENSSL_free(buf); - if(status) rb_jump_tag(status); - + if (state) + rb_jump_tag(state); return str; } +void +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]; + + out[i * 2 + 0] = hex[p >> 4]; + out[i * 2 + 1] = hex[p & 0x0f]; + } +} + /* * our default PEM callback */ -static VALUE -ossl_pem_passwd_cb0(VALUE flag) -{ - VALUE pass; +VALUE +ossl_pem_passwd_value(VALUE pass) +{ + if (NIL_P(pass)) + return Qnil; + + StringValue(pass); - pass = rb_yield(flag); - SafeStringValue(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); return pass; } +static VALUE +ossl_pem_passwd_cb0(VALUE flag) +{ + VALUE pass = rb_yield(flag); + if (NIL_P(pass)) + return Qnil; + StringValue(pass); + return pass; +} + int -ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd) +ossl_pem_passwd_cb(char *buf, int max_len, int flag, void *pwd_) { - int len, status = 0; - VALUE rflag, pass; - - if (pwd || !rb_block_given_p()) - return PEM_def_callback(buf, max_len, flag, pwd); + long len; + int status; + VALUE rflag, pass = (VALUE)pwd_; - 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) return -1; /* exception was raised. */ - len = RSTRING(pass)->len; - if (len < 4) { /* 4 is OpenSSL hardcoded limit */ - rb_warning("password must be longer than 4 bytes"); - continue; - } - if (len > max_len) { - rb_warning("password must be shorter then %d bytes", max_len-1); - continue; - } - memcpy(buf, RSTRING(pass)->ptr, len); - break; + 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; } - return len; -} -/* - * Verify callback - */ -int ossl_verify_cb_idx; - -VALUE -ossl_call_verify_cb_proc(struct ossl_verify_cb_args *args) -{ - return rb_funcall(args->proc, rb_intern("call"), 2, - args->preverify_ok, args->store_ctx); -} - -int -ossl_verify_cb(int ok, X509_STORE_CTX *ctx) -{ - VALUE proc, rctx, ret; - struct ossl_verify_cb_args args; - int state = 0; - - proc = (VALUE)X509_STORE_CTX_get_ex_data(ctx, ossl_verify_cb_idx); - if ((void*)proc == 0) - proc = (VALUE)X509_STORE_get_ex_data(ctx->ctx, ossl_verify_cb_idx); - if ((void*)proc == 0) - return ok; - if (!NIL_P(proc)) { - rctx = rb_protect((VALUE(*)(VALUE))ossl_x509stctx_new, - (VALUE)ctx, &state); - ret = Qfalse; - if (!state) { - args.proc = proc; - args.preverify_ok = ok ? Qtrue : Qfalse; - args.store_ctx = rctx; - ret = rb_ensure(ossl_call_verify_cb_proc, (VALUE)&args, - ossl_x509stctx_clear_ptr, rctx); - } - if (ret == Qtrue) { - X509_STORE_CTX_set_error(ctx, X509_V_OK); - ok = 1; - } - else{ - if (X509_STORE_CTX_get_error(ctx) == X509_V_OK) { - X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED); - } - ok = 0; - } + if (!rb_block_given_p()) { + return PEM_def_callback(buf, max_len, flag, NULL); } - return ok; + while (1) { + /* + * 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; } /* @@ -248,7 +234,7 @@ VALUE eOSSLError; /* * Convert to DER string */ -ID ossl_s_to_der; +static ID ossl_s_to_der; VALUE ossl_to_der(VALUE obj) @@ -265,53 +251,158 @@ 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 ID id_i_errors; + +static void collect_errors_into(VALUE ary); + +VALUE +ossl_make_error(VALUE exc, VALUE str) +{ + unsigned long e; + const char *data; + int flags; + VALUE errors = rb_ary_new(); + + 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 (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); + } + + VALUE obj = rb_exc_new_str(exc, str); + rb_ivar_set(obj, id_i_errors, errors); + return obj; +} + void ossl_raise(VALUE exc, const char *fmt, ...) { va_list args; - char buf[BUFSIZ]; - const char *msg; - long e; - int len = 0; + VALUE err; -#ifdef HAVE_ERR_PEEK_LAST_ERROR - e = ERR_peek_last_error(); -#else - e = ERR_peek_error(); -#endif if (fmt) { - va_start(args, fmt); - len = vsnprintf(buf, BUFSIZ, fmt, args); - va_end(args); + va_start(args, fmt); + err = rb_vsprintf(fmt, args); + va_end(args); } - if (len < BUFSIZ && e) { - if (dOSSL == Qtrue) /* FULL INFO */ - msg = ERR_error_string(e, NULL); - else - msg = ERR_reason_error_string(e); - fmt = len ? ": %s" : "%s"; - len += snprintf(buf+len, BUFSIZ-len, fmt, msg); + else { + err = Qnil; } - if (dOSSL == Qtrue){ /* show all errors on the stack */ - while ((e = ERR_get_error()) != 0){ - rb_warn("error on stack: %s", ERR_error_string(e, NULL)); - } + + rb_exc_raise(ossl_make_error(exc, err)); +} + +static void +collect_errors_into(VALUE ary) +{ + 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(); +} - if(len > BUFSIZ) len = strlen(buf); - rb_exc_raise(rb_exc_new(exc, buf, len)); +void +ossl_clear_error(void) +{ + collect_errors_into(Qnil); } -VALUE -ossl_get_errors() +/* + * 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...] + * + * 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. + * + * For debugging your program, OpenSSL.debug= may be useful. + */ +static VALUE +ossl_get_errors(VALUE _) { VALUE ary; long e; @@ -329,52 +420,594 @@ ossl_get_errors() */ VALUE dOSSL; -#if !defined(HAVE_VA_ARGS_MACRO) -void -ossl_debug(const char *fmt, ...) +/* + * call-seq: + * OpenSSL.debug -> true | false + * + * Returns whether Ruby/OpenSSL's debug mode is currently enabled. + */ +static VALUE +ossl_debug_get(VALUE self) { - 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"); - } + return dOSSL; } + +/* + * call-seq: + * OpenSSL.debug = boolean + * + * Turns on or off debug mode. With debug mode, all errors added to the \OpenSSL + * error queue will be printed to stderr. + */ +static VALUE +ossl_debug_set(VALUE self, VALUE val) +{ + dOSSL = RTEST(val) ? Qtrue : Qfalse; + + return val; +} + +/* + * call-seq: + * OpenSSL.fips_mode -> true | false + * + * Returns whether the FIPS mode is currently enabled. + */ +static VALUE +ossl_fips_mode_get(VALUE self) +{ + +#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; +#else + return Qfalse; #endif +} +/* + * call-seq: + * 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 + * so otherwise will result in an error. + * + * === Examples + * OpenSSL.fips_mode = true # turn FIPS mode on + * OpenSSL.fips_mode = false # and off again + */ static VALUE -ossl_debug_get(VALUE self) +ossl_fips_mode_set(VALUE self, VALUE enabled) { - return dOSSL; +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + if (RTEST(enabled)) { + if (!EVP_default_properties_enable_fips(NULL, 1)) { + ossl_raise(eOSSLError, "Turning on FIPS mode failed"); + } + } else { + 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"); + return enabled; +#endif } +/* + * call-seq: + * OpenSSL.fixed_length_secure_compare(string, string) -> true or false + * + * Constant time memory comparison for fixed length strings, such as results + * of \HMAC calculations. + * + * 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 -ossl_debug_set(VALUE self, VALUE val) +ossl_crypto_fixed_length_secure_compare(VALUE dummy, VALUE str1, VALUE str2) { - VALUE old = dOSSL; - dOSSL = val; - - if (old != dOSSL) { - if (dOSSL == Qtrue) { - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); - fprintf(stderr, "OSSL_DEBUG: IS NOW ON!\n"); - } else if (old == Qtrue) { - CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF); - fprintf(stderr, "OSSL_DEBUG: IS NOW OFF!\n"); - } + const unsigned char *p1; + const unsigned char *p2; + long len1; + long len2; + + StringValue(str1); + StringValue(str2); + + p1 = (const unsigned char *)RSTRING_PTR(str1); + p2 = (const unsigned char *)RSTRING_PTR(str2); + len1 = RSTRING_LEN(str1); + len2 = RSTRING_LEN(str2); + + if (len1 != len2) { + ossl_raise(rb_eArgError, "inputs must be of equal length"); + } + + switch (CRYPTO_memcmp(p1, p2, len1)) { + case 0: return Qtrue; + default: return Qfalse; } - return val; } /* - * OSSL library init + * OpenSSL provides \SSL, TLS and general purpose cryptography. It wraps the + * OpenSSL[https://www.openssl.org/] library. + * + * = Examples + * + * All examples assume you have loaded OpenSSL with: + * + * require 'openssl' + * + * These examples build atop each other. For example the key created in the + * next is used in throughout these examples. + * + * == Keys + * + * === Creating a Key + * + * This example creates a 2048 bit RSA keypair and writes it to the current + * directory. + * + * key = OpenSSL::PKey::RSA.new 2048 + * + * 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 password. + * + * cipher = OpenSSL::Cipher.new 'aes-256-cbc' + * password = 'my secure password goes here' + * + * key_secure = key.private_to_pem cipher, password + * + * File.write 'private.secure.pem', key_secure + * + * OpenSSL::Cipher.ciphers returns a list of available ciphers. + * + * === Loading a Key + * + * A key can also be loaded from a file. + * + * key2 = OpenSSL::PKey.read File.read 'private_key.pem' + * key2.public? # => true + * key2.private? # => true + * + * or + * + * 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 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' + * password = 'my secure password goes here' + * key4 = OpenSSL::PKey.read key4_pem, password + * + * == RSA Encryption + * + * RSA provides encryption and decryption using the public and private keys. + * You can use a variety of padding methods depending upon the intended use of + * encrypted data. + * + * === Encryption & Decryption + * + * Asymmetric public/private key encryption is slow and victim to attack in + * cases where it is used without padding or directly to encrypt larger chunks + * of data. Typical use cases for RSA encryption involve "wrapping" a symmetric + * key with the public key of the recipient who would "unwrap" that symmetric + * key again using their private key. + * The following illustrates a simplified example of such a key transport + * scheme. It shouldn't be used in practice, though, standardized protocols + * should always be preferred. + * + * wrapped_key = key.public_encrypt key + * + * A symmetric key encrypted with the public key can only be decrypted with + * the corresponding private key of the recipient. + * + * original_key = key.private_decrypt wrapped_key + * + * By default PKCS#1 padding will be used, but it is also possible to use + * other forms of padding, see PKey::RSA for further details. + * + * === Signatures + * + * Using "private_encrypt" to encrypt some data with the private key is + * equivalent to applying a digital signature to the data. A verifying + * party may validate the signature by comparing the result of decrypting + * the signature with "public_decrypt" to the original data. However, + * OpenSSL::PKey already has methods "sign" and "verify" that handle + * digital signatures in a standardized way - "private_encrypt" and + * "public_decrypt" shouldn't be used in practice. + * + * To sign a document, a cryptographically secure hash of the document is + * computed first, which is then signed using the private key. + * + * 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. + * + * if key.verify 'SHA256', signature, document + * puts 'Valid' + * else + * puts 'Invalid' + * end + * + * == PBKDF2 Password-based Encryption + * + * 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). + * + * PKCS5 supports PBKDF2 as it was specified in PKCS#5 + * v2.0[http://www.rsa.com/rsalabs/node.asp?id=2127]. It still uses a + * password, a salt, and additionally a number of iterations that will + * slow the key derivation process down. The slower this is, the more work + * it requires being able to brute-force the resulting key. + * + * === Encryption + * + * The strategy is to first instantiate a Cipher for encryption, and + * then to generate a random IV plus a key derived from the password + * 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-256-cbc' + * cipher.encrypt + * iv = cipher.random_iv + * + * pwd = 'some hopefully not to easily guessable password' + * salt = OpenSSL::Random.random_bytes 16 + * iter = 20000 + * key_len = cipher.key_len + * digest = OpenSSL::Digest.new('SHA256') + * + * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) + * cipher.key = key + * + * Now encrypt the data: + * + * encrypted = cipher.update document + * encrypted << cipher.final + * + * === Decryption + * + * 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-256-cbc' + * cipher.decrypt + * cipher.iv = iv # the one generated with #random_iv + * + * pwd = 'some hopefully not to easily guessable password' + * salt = ... # the one generated above + * iter = 20000 + * key_len = cipher.key_len + * digest = OpenSSL::Digest.new('SHA256') + * + * key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) + * cipher.key = key + * + * Now decrypt the data: + * + * decrypted = cipher.update encrypted + * decrypted << cipher.final + * + * == \X509 Certificates + * + * === Creating a Certificate + * + * This example creates a self-signed certificate using an RSA key and a SHA1 + * signature. + * + * key = OpenSSL::PKey::RSA.new 2048 + * name = OpenSSL::X509::Name.parse '/CN=nobody/DC=example' + * + * cert = OpenSSL::X509::Certificate.new + * cert.version = 2 + * cert.serial = 0 + * cert.not_before = Time.now + * cert.not_after = Time.now + 3600 + * + * cert.public_key = key.public_key + * cert.subject = name + * + * === Certificate Extensions + * + * You can add extensions to the certificate with + * OpenSSL::SSL::ExtensionFactory to indicate the purpose of the certificate. + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert + * + * cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:FALSE', true) + * + * cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') + * + * cert.add_extension \ + * 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. + * + * === Signing a Certificate + * + * To sign a certificate set the issuer and use OpenSSL::X509::Certificate#sign + * with a digest algorithm. This creates a self-signed cert because we're using + * the same name and key to sign the certificate as was used to create the + * certificate. + * + * cert.issuer = name + * cert.sign key, OpenSSL::Digest.new('SHA1') + * + * open 'certificate.pem', 'w' do |io| io.write cert.to_pem end + * + * === Loading a Certificate + * + * Like a key, a cert can also be loaded from a file. + * + * cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem' + * + * === Verifying a Certificate + * + * Certificate#verify will return true when a certificate was signed with the + * given public key. + * + * raise 'certificate can not be verified' unless cert2.verify key + * + * == Certificate Authority + * + * A certificate authority (CA) is a trusted third party that allows you to + * verify the ownership of unknown certificates. The CA issues key signatures + * that indicate it trusts the user of that key. A user encountering the key + * can verify the signature by using the CA's public key. + * + * === CA Key + * + * CA keys are valuable, so we encrypt and save it to disk and make sure it is + * not readable by other users. + * + * ca_key = OpenSSL::PKey::RSA.new 2048 + * password = 'my secure password goes here' + * + * cipher = 'aes-256-cbc' + * + * open 'ca_key.pem', 'w', 0400 do |io| + * io.write ca_key.private_to_pem(cipher, password) + * end + * + * === CA Certificate + * + * 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_cert = OpenSSL::X509::Certificate.new + * ca_cert.serial = 0 + * ca_cert.version = 2 + * ca_cert.not_before = Time.now + * ca_cert.not_after = Time.now + 86400 + * + * ca_cert.public_key = ca_key.public_key + * ca_cert.subject = ca_name + * ca_cert.issuer = ca_name + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new + * extension_factory.subject_certificate = ca_cert + * extension_factory.issuer_certificate = ca_cert + * + * ca_cert.add_extension \ + * extension_factory.create_extension('subjectKeyIdentifier', 'hash') + * + * This extension indicates the CA's key may be used as a CA. + * + * ca_cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:TRUE', true) + * + * This extension indicates the CA's key may be used to verify signatures on + * both certificates and certificate revocations. + * + * ca_cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'cRLSign,keyCertSign', true) + * + * Root CA certificates are self-signed. + * + * 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. + * + * open 'ca_cert.pem', 'w' do |io| + * io.write ca_cert.to_pem + * end + * + * === Certificate Signing Request + * + * The CA signs keys through a Certificate Signing Request (CSR). The CSR + * contains the information necessary to identify the key. + * + * csr = OpenSSL::X509::Request.new + * csr.version = 0 + * csr.subject = name + * csr.public_key = key.public_key + * csr.sign key, OpenSSL::Digest.new('SHA1') + * + * A CSR is saved to disk and sent to the CA for signing. + * + * open 'csr.pem', 'w' do |io| + * io.write csr.to_pem + * end + * + * === Creating a Certificate from a CSR + * + * Upon receiving a CSR the CA will verify it before signing it. A minimal + * verification would be to check the CSR's signature. + * + * csr = OpenSSL::X509::Request.new File.read 'csr.pem' + * + * raise 'CSR can not be verified' unless csr.verify csr.public_key + * + * After verification a certificate is created, marked for various usages, + * signed with the CA key and returned to the requester. + * + * csr_cert = OpenSSL::X509::Certificate.new + * csr_cert.serial = 0 + * csr_cert.version = 2 + * csr_cert.not_before = Time.now + * csr_cert.not_after = Time.now + 600 + * + * csr_cert.subject = csr.subject + * csr_cert.public_key = csr.public_key + * csr_cert.issuer = ca_cert.subject + * + * extension_factory = OpenSSL::X509::ExtensionFactory.new + * extension_factory.subject_certificate = csr_cert + * extension_factory.issuer_certificate = ca_cert + * + * csr_cert.add_extension \ + * extension_factory.create_extension('basicConstraints', 'CA:FALSE') + * + * csr_cert.add_extension \ + * extension_factory.create_extension( + * 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') + * + * csr_cert.add_extension \ + * extension_factory.create_extension('subjectKeyIdentifier', 'hash') + * + * 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 + * + * 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 + * + * An \SSL server requires the certificate and private key to communicate + * securely with its clients: + * + * context.cert = cert + * context.key = key + * + * Then create an OpenSSL::SSL::SSLServer with a TCP server socket and the + * context. Use the SSLServer like an ordinary TCP server. + * + * require 'socket' + * + * tcp_server = TCPServer.new 5000 + * ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context + * + * loop do + * ssl_connection = ssl_server.accept + * + * data = ssl_connection.gets + * + * response = "I got #{data.dump}" + * puts response + * + * ssl_connection.puts "I got #{data.dump}" + * ssl_connection.close + * end + * + * === \SSL client + * + * 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 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' + * + * tcp_socket = TCPSocket.new 'localhost', 5000 + * ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context + * ssl_client.sync_close = true + * ssl_client.connect + * + * ssl_client.puts "hello server!" + * puts ssl_client.gets + * + * ssl_client.close # shutdown the TLS connection and close tcp_socket + * + * === Peer Verification + * + * 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 + * certificate authority's certificate: + * + * context.ca_file = 'ca_cert.pem' + * context.verify_mode = OpenSSL::SSL::VERIFY_PEER + * + * require 'socket' + * + * tcp_socket = TCPSocket.new 'localhost', 5000 + * ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context + * ssl_client.connect + * + * ssl_client.puts "hello server!" + * puts ssl_client.gets + * + * If the server certificate is invalid or <tt>context.ca_file</tt> is not set + * when verifying peers an OpenSSL::SSL::SSLError will be raised. + * */ void -Init_openssl() +Init_openssl(void) { +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + +#undef rb_intern /* * Init timezone info */ @@ -385,55 +1018,116 @@ Init_openssl() /* * 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(); + if (!OPENSSL_init_ssl(0, NULL)) + rb_raise(rb_eRuntimeError, "OPENSSL_init_ssl"); /* - * FIXME: - * On unload do: + * Init main module */ -#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 + 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); /* - * Init main module + * \OpenSSL library version string used to compile the Ruby/OpenSSL + * extension. This may differ from the version used at runtime. */ - mOSSL = rb_define_module("OpenSSL"); + rb_define_const(mOSSL, "OPENSSL_VERSION", + rb_obj_freeze(rb_str_new_cstr(OPENSSL_VERSION_TEXT))); + + /* + * \OpenSSL library version string currently used at runtime. + */ + rb_define_const( + mOSSL, + "OPENSSL_LIBRARY_VERSION", + rb_obj_freeze(rb_str_new_cstr(OpenSSL_version(OPENSSL_VERSION))) + ); /* - * Constants + * \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, "VERSION", rb_str_new2(OSSL_VERSION)); - rb_define_const(mOSSL, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT)); rb_define_const(mOSSL, "OPENSSL_VERSION_NUMBER", INT2NUM(OPENSSL_VERSION_NUMBER)); +#if defined(LIBRESSL_VERSION_NUMBER) /* - * Generic error, - * common for all classes under OpenSSL module + * 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). */ - eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError); + rb_define_const(mOSSL, "LIBRESSL_VERSION_NUMBER", INT2NUM(LIBRESSL_VERSION_NUMBER)); +#endif + + /* + * 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", +/* 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 +#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 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); /* - * Verify callback Proc index for ext-data + * \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). */ - ossl_verify_cb_idx = - X509_STORE_CTX_get_ex_new_index(0, "ossl_verify_cb_idx", 0, 0, 0); + rb_attr(eOSSLError, rb_intern_const("errors"), 1, 0, 0); + rb_define_method(eOSSLError, "detailed_message", osslerror_detailed_message, -1); /* * Init debug core */ dOSSL = Qfalse; + rb_global_variable(&dOSSL); + rb_define_module_function(mOSSL, "debug", ossl_debug_get, 0); rb_define_module_function(mOSSL, "debug=", ossl_debug_set, 1); rb_define_module_function(mOSSL, "errors", ossl_get_errors, 0); @@ -442,35 +1136,27 @@ Init_openssl() * Get ID of to_der */ ossl_s_to_der = rb_intern("to_der"); + 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(); } - -#if defined(OSSL_DEBUG) -/* - * Check if all symbols are OK with 'make LDSHARED=gcc all' - */ -int -main(int argc, char *argv[], char *env[]) -{ - return 0; -} -#endif /* OSSL_DEBUG */ - |
