diff options
Diffstat (limited to 'ext/openssl/ossl_digest.c')
| -rw-r--r-- | ext/openssl/ossl_digest.c | 496 |
1 files changed, 339 insertions, 157 deletions
diff --git a/ext/openssl/ossl_digest.c b/ext/openssl/ossl_digest.c index b117ddd30d..e23968b1e3 100644 --- a/ext/openssl/ossl_digest.c +++ b/ext/openssl/ossl_digest.c @@ -1,59 +1,121 @@ /* - * $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" #define GetDigest(obj, ctx) do { \ - Data_Get_Struct(obj, EVP_MD_CTX, ctx); \ - if (!ctx) { \ - ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ + TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_digest_type, (ctx)); \ + if (!(ctx)) { \ + ossl_raise(rb_eRuntimeError, "Digest CTX wasn't initialized!"); \ } \ } while (0) -#define SafeGetDigest(obj, ctx) do { \ - OSSL_Check_Kind(obj, cDigest); \ - GetDigest(obj, ctx); \ -} while (0) /* * Classes */ -VALUE mDigest; -VALUE cDigest; -VALUE eDigestError; +static VALUE cDigest; +static VALUE eDigestError; +static ID id_md_holder; static VALUE ossl_digest_alloc(VALUE klass); +static void +ossl_digest_free(void *ctx) +{ + EVP_MD_CTX_destroy(ctx); +} + +static const rb_data_type_t ossl_digest_type = { + "OpenSSL/Digest", + { + 0, ossl_digest_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +#ifdef OSSL_USE_PROVIDER +static void +ossl_evp_md_free(void *ptr) +{ + // This is safe to call against const EVP_MD * returned by + // EVP_get_digestbyname() + EVP_MD_free(ptr); +} + +static const rb_data_type_t ossl_evp_md_holder_type = { + "OpenSSL/EVP_MD", + { + .dfree = ossl_evp_md_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; +#endif + /* * Public */ const EVP_MD * -GetDigestPtr(VALUE obj) +ossl_evp_md_fetch(VALUE obj, volatile VALUE *holder) { - EVP_MD_CTX *ctx; - - SafeGetDigest(obj, ctx); + *holder = Qnil; + if (rb_obj_is_kind_of(obj, cDigest)) { + EVP_MD_CTX *ctx; + GetDigest(obj, ctx); + EVP_MD *md = (EVP_MD *)EVP_MD_CTX_get0_md(ctx); +#ifdef OSSL_USE_PROVIDER + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + if (!EVP_MD_up_ref(md)) + ossl_raise(eDigestError, "EVP_MD_up_ref"); + RTYPEDDATA_DATA(*holder) = md; +#endif + return md; + } - return EVP_MD_CTX_md(ctx); /*== ctx->digest*/ + const char *name = StringValueCStr(obj); + EVP_MD *md = (EVP_MD *)EVP_get_digestbyname(name); + if (!md) { + ASN1_OBJECT *oid = OBJ_txt2obj(name, 0); + md = (EVP_MD *)EVP_get_digestbyobj(oid); + ASN1_OBJECT_free(oid); + } +#ifdef OSSL_USE_PROVIDER + if (!md) { + ossl_clear_error(); + *holder = TypedData_Wrap_Struct(0, &ossl_evp_md_holder_type, NULL); + md = EVP_MD_fetch(NULL, name, NULL); + RTYPEDDATA_DATA(*holder) = md; + } +#endif + if (!md) + ossl_raise(eDigestError, "unsupported digest algorithm: %"PRIsVALUE, + obj); + return md; } VALUE ossl_digest_new(const EVP_MD *md) -{ +{ VALUE ret; EVP_MD_CTX *ctx; + // NOTE: This does not set id_md_holder because this function should + // only be called from ossl_engine.c, which will not use any + // reference-counted digests. ret = ossl_digest_alloc(cDigest); - GetDigest(ret, ctx); - EVP_MD_CTX_init(ctx); - EVP_DigestInit(ctx, md); - + ctx = EVP_MD_CTX_new(); + if (!ctx) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); + RTYPEDDATA_DATA(ret) = ctx; + + if (!EVP_DigestInit_ex(ctx, md, NULL)) + ossl_raise(eDigestError, "Digest initialization failed"); + return ret; } @@ -63,231 +125,351 @@ ossl_digest_new(const EVP_MD *md) static VALUE ossl_digest_alloc(VALUE klass) { - EVP_MD_CTX *ctx; - VALUE obj; - - ctx = EVP_MD_CTX_create(); - if (ctx == NULL) - ossl_raise(rb_eRuntimeError, "EVP_MD_CTX_create() failed"); - obj = Data_Wrap_Struct(klass, 0, EVP_MD_CTX_destroy, ctx); - - return obj; + return TypedData_Wrap_Struct(klass, &ossl_digest_type, 0); } -VALUE ossl_digest_update(VALUE, VALUE); +static VALUE ossl_digest_update(VALUE, VALUE); +/* + * call-seq: + * Digest.new(string [, data]) -> Digest + * + * Creates a Digest instance based on _string_, which is either the ln + * (long name) or sn (short name) of a supported digest algorithm. A list of + * supported algorithms can be obtained by calling OpenSSL::Digest.digests. + * + * If _data_ (a String) is given, it is used as the initial input to the + * Digest instance, i.e. + * + * digest = OpenSSL::Digest.new('sha256', 'digestdata') + * + * is equivalent to + * + * digest = OpenSSL::Digest.new('sha256') + * digest.update('digestdata') + */ static VALUE ossl_digest_initialize(int argc, VALUE *argv, VALUE self) { EVP_MD_CTX *ctx; const EVP_MD *md; - char *name; - VALUE type, data; - - GetDigest(self, ctx); + VALUE type, data, md_holder; rb_scan_args(argc, argv, "11", &type, &data); - name = StringValuePtr(type); + md = ossl_evp_md_fetch(type, &md_holder); if (!NIL_P(data)) StringValue(data); - - md = EVP_get_digestbyname(name); - if (!md) { - ossl_raise(rb_eRuntimeError, "Unsupported digest algorithm (%s).", name); + + TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx); + if (!ctx) { + RTYPEDDATA_DATA(self) = ctx = EVP_MD_CTX_new(); + if (!ctx) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); } - EVP_MD_CTX_init(ctx); - EVP_DigestInit(ctx, md); - + + if (!EVP_DigestInit_ex(ctx, md, NULL)) + ossl_raise(eDigestError, "Digest initialization failed"); + rb_ivar_set(self, id_md_holder, md_holder); + if (!NIL_P(data)) return ossl_digest_update(self, data); return self; } +/* :nodoc: */ static VALUE ossl_digest_copy(VALUE self, VALUE other) { EVP_MD_CTX *ctx1, *ctx2; - + rb_check_frozen(self); if (self == other) return self; - GetDigest(self, ctx1); - SafeGetDigest(other, ctx2); + TypedData_Get_Struct(self, EVP_MD_CTX, &ossl_digest_type, ctx1); + if (!ctx1) { + RTYPEDDATA_DATA(self) = ctx1 = EVP_MD_CTX_new(); + if (!ctx1) + ossl_raise(eDigestError, "EVP_MD_CTX_new"); + } + GetDigest(other, ctx2); if (!EVP_MD_CTX_copy(ctx1, ctx2)) { - ossl_raise(eDigestError, NULL); + ossl_raise(eDigestError, NULL); } return self; } -static VALUE -ossl_digest_reset(VALUE self) -{ - EVP_MD_CTX *ctx; - - GetDigest(self, ctx); - EVP_DigestInit(ctx, EVP_MD_CTX_md(ctx)); - - return self; -} - -VALUE -ossl_digest_update(VALUE self, VALUE data) -{ - EVP_MD_CTX *ctx; - - GetDigest(self, ctx); - StringValue(data); - EVP_DigestUpdate(ctx, RSTRING(data)->ptr, RSTRING(data)->len); - - return self; -} - static void -digest_final(EVP_MD_CTX *ctx, char **buf, int *buf_len) +add_digest_name_to_ary(const OBJ_NAME *name, void *arg) { - EVP_MD_CTX final; - - if (!EVP_MD_CTX_copy(&final, ctx)) { - ossl_raise(eDigestError, NULL); - } - if (!(*buf = OPENSSL_malloc(EVP_MD_CTX_size(&final)))) { - ossl_raise(eDigestError, "Cannot allocate mem for digest"); - } - EVP_DigestFinal(&final, *buf, buf_len); - EVP_MD_CTX_cleanup(&final); + VALUE ary = (VALUE)arg; + rb_ary_push(ary, rb_str_new2(name->name)); } +/* + * call-seq: + * OpenSSL::Digest.digests -> array[string...] + * + * Returns the names of all available digests in an array. + */ static VALUE -ossl_digest_digest(VALUE self) +ossl_s_digests(VALUE self) { - EVP_MD_CTX *ctx; - char *buf; - int buf_len; - VALUE digest; - - GetDigest(self, ctx); - digest_final(ctx, &buf, &buf_len); - digest = ossl_buf2str(buf, buf_len); - - return digest; + VALUE ary; + + ary = rb_ary_new(); + OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_MD_METH, + add_digest_name_to_ary, + (void*)ary); + + return ary; } +/* + * call-seq: + * digest.reset -> self + * + * Resets the Digest in the sense that any Digest#update that has been + * performed is abandoned and the Digest is set to its initial state again. + * + */ static VALUE -ossl_digest_hexdigest(VALUE self) +ossl_digest_reset(VALUE self) { EVP_MD_CTX *ctx; - char *buf, *hexbuf; - int buf_len; - VALUE hexdigest; GetDigest(self, ctx); - digest_final(ctx, &buf, &buf_len); - if (string2hex(buf, buf_len, &hexbuf, NULL) != 2 * buf_len) { - OPENSSL_free(buf); - ossl_raise(eDigestError, "Memory alloc error"); + if (EVP_DigestInit_ex(ctx, EVP_MD_CTX_get0_md(ctx), NULL) != 1) { + ossl_raise(eDigestError, "Digest initialization failed."); } - OPENSSL_free(buf); - hexdigest = ossl_buf2str(hexbuf, 2 * buf_len); - return hexdigest; + return self; } +/* + * call-seq: + * digest.update(string) -> aString + * + * Not every message digest can be computed in one single pass. If a message + * digest is to be computed from several subsequent sources, then each may + * be passed individually to the Digest instance. + * + * === Example + * digest = OpenSSL::Digest.new('SHA256') + * digest.update('First input') + * digest << 'Second input' # equivalent to digest.update('Second input') + * result = digest.digest + * + */ static VALUE -ossl_digest_s_digest(VALUE klass, VALUE str, VALUE data) +ossl_digest_update(VALUE self, VALUE data) { - VALUE obj = rb_class_new_instance(1, &str, klass); + EVP_MD_CTX *ctx; + + StringValue(data); + GetDigest(self, ctx); - ossl_digest_update(obj, data); + if (!EVP_DigestUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data))) + ossl_raise(eDigestError, "EVP_DigestUpdate"); - return ossl_digest_digest(obj); + return self; } +/* + * call-seq: + * digest.finish -> aString + * + */ static VALUE -ossl_digest_s_hexdigest(VALUE klass, VALUE str, VALUE data) +ossl_digest_finish(VALUE self) { - VALUE obj = rb_class_new_instance(1, &str, klass); + EVP_MD_CTX *ctx; + VALUE str; - ossl_digest_update(obj, data); + GetDigest(self, ctx); + str = rb_str_new(NULL, EVP_MD_CTX_size(ctx)); + if (!EVP_DigestFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), NULL)) + ossl_raise(eDigestError, "EVP_DigestFinal_ex"); - return ossl_digest_hexdigest(obj); + return str; } +/* + * call-seq: + * digest.name -> string + * + * Returns the short name of this Digest algorithm which may differ slightly + * from the original name provided. + * + * === Example + * digest = OpenSSL::Digest.new('SHA512') + * puts digest.name # => SHA512 + * + */ static VALUE -ossl_digest_equal(VALUE self, VALUE other) +ossl_digest_name(VALUE self) { EVP_MD_CTX *ctx; - VALUE str1, str2; GetDigest(self, ctx); - if (rb_obj_is_kind_of(other, cDigest) == Qtrue) { - str2 = ossl_digest_digest(other); - } else { - StringValue(other); - str2 = other; - } - if (RSTRING(str2)->len == EVP_MD_CTX_size(ctx)) { - str1 = ossl_digest_digest(self); - } else { - str1 = ossl_digest_hexdigest(self); - } - if (RSTRING(str1)->len == RSTRING(str2)->len - && rb_str_cmp(str1, str2) == 0) { - return Qtrue; - } - return Qfalse; + return rb_str_new_cstr(EVP_MD_name(EVP_MD_CTX_get0_md(ctx))); } +/* + * call-seq: + * digest.digest_length -> integer + * + * Returns the output size of the digest, i.e. the length in bytes of the + * final message digest result. + * + * === Example + * digest = OpenSSL::Digest.new('SHA1') + * puts digest.digest_length # => 20 + * + */ static VALUE -ossl_digest_name(VALUE self) +ossl_digest_size(VALUE self) { EVP_MD_CTX *ctx; GetDigest(self, ctx); - return rb_str_new2(EVP_MD_name(EVP_MD_CTX_md(ctx))); + return INT2NUM(EVP_MD_CTX_size(ctx)); } +/* + * call-seq: + * digest.block_length -> integer + * + * Returns the block length of the digest algorithm, i.e. the length in bytes + * of an individual block. Most modern algorithms partition a message to be + * digested into a sequence of fix-sized blocks that are processed + * consecutively. + * + * === Example + * digest = OpenSSL::Digest.new('SHA1') + * puts digest.block_length # => 64 + */ static VALUE -ossl_digest_size(VALUE self) +ossl_digest_block_length(VALUE self) { EVP_MD_CTX *ctx; GetDigest(self, ctx); - return INT2NUM(EVP_MD_CTX_size(ctx)); + return INT2NUM(EVP_MD_CTX_block_size(ctx)); } /* * INIT */ void -Init_ossl_digest() +Init_ossl_digest(void) { - mDigest = rb_define_module_under(mOSSL, "Digest"); - - eDigestError = rb_define_class_under(mDigest, "DigestError", eOSSLError); - - cDigest = rb_define_class_under(mDigest, "Digest", rb_cObject); - + /* Document-class: OpenSSL::Digest + * + * OpenSSL::Digest allows you to compute message digests (sometimes + * interchangeably called "hashes") of arbitrary data that are + * cryptographically secure, i.e. a Digest implements a secure one-way + * function. + * + * One-way functions offer some useful properties. E.g. given two + * distinct inputs the probability that both yield the same output + * is highly unlikely. Combined with the fact that every message digest + * algorithm has a fixed-length output of just a few bytes, digests are + * often used to create unique identifiers for arbitrary data. A common + * example is the creation of a unique id for binary documents that are + * stored in a database. + * + * Another useful characteristic of one-way functions (and thus the name) + * is that given a digest there is no indication about the original + * data that produced it, i.e. the only way to identify the original input + * is to "brute-force" through every possible combination of inputs. + * + * These characteristics make one-way functions also ideal companions + * for public key signature algorithms: instead of signing an entire + * document, first a hash of the document is produced with a considerably + * faster message digest algorithm and only the few bytes of its output + * need to be signed using the slower public key algorithm. To validate + * the integrity of a signed document, it suffices to re-compute the hash + * and verify that it is equal to that in the signature. + * + * You can get a list of all digest algorithms supported on your system by + * running this command in your terminal: + * + * openssl list -digest-algorithms + * + * Among the OpenSSL 1.1.1 supported message digest algorithms are: + * * SHA224, SHA256, SHA384, SHA512, SHA512-224 and SHA512-256 + * * SHA3-224, SHA3-256, SHA3-384 and SHA3-512 + * * BLAKE2s256 and BLAKE2b512 + * + * Each of these algorithms can be instantiated using the name: + * + * digest = OpenSSL::Digest.new('SHA256') + * + * "Breaking" a message digest algorithm means defying its one-way + * function characteristics, i.e. producing a collision or finding a way + * to get to the original data by means that are more efficient than + * brute-forcing etc. Most of the supported digest algorithms can be + * considered broken in this sense, even the very popular MD5 and SHA1 + * algorithms. Should security be your highest concern, then you should + * probably rely on SHA224, SHA256, SHA384 or SHA512. + * + * === Hashing a file + * + * data = File.binread('document') + * sha256 = OpenSSL::Digest.new('SHA256') + * digest = sha256.digest(data) + * + * === Hashing several pieces of data at once + * + * data1 = File.binread('file1') + * data2 = File.binread('file2') + * data3 = File.binread('file3') + * sha256 = OpenSSL::Digest.new('SHA256') + * sha256 << data1 + * sha256 << data2 + * sha256 << data3 + * digest = sha256.digest + * + * === Reuse a Digest instance + * + * data1 = File.binread('file1') + * sha256 = OpenSSL::Digest.new('SHA256') + * digest1 = sha256.digest(data1) + * + * data2 = File.binread('file2') + * sha256.reset + * digest2 = sha256.digest(data2) + * + */ + + /* + * Digest::Class is defined by the digest library. rb_require() cannot be + * used here because it bypasses RubyGems. + */ + rb_funcall(Qnil, rb_intern_const("require"), 1, rb_str_new_cstr("digest")); + cDigest = rb_define_class_under(mOSSL, "Digest", rb_path2class("Digest::Class")); + /* Document-class: OpenSSL::Digest::DigestError + * + * Generic Exception class that is raised if an error occurs during a + * Digest operation. + */ + eDigestError = rb_define_class_under(cDigest, "DigestError", eOSSLError); + rb_define_alloc_func(cDigest, ossl_digest_alloc); - rb_define_singleton_method(cDigest, "digest", ossl_digest_s_digest, 2); - rb_define_singleton_method(cDigest, "hexdigest", ossl_digest_s_hexdigest, 2); - + + rb_define_module_function(cDigest, "digests", ossl_s_digests, 0); rb_define_method(cDigest, "initialize", ossl_digest_initialize, -1); + rb_define_method(cDigest, "initialize_copy", ossl_digest_copy, 1); rb_define_method(cDigest, "reset", ossl_digest_reset, 0); - - rb_define_copy_func(cDigest, ossl_digest_copy); - - rb_define_method(cDigest, "digest", ossl_digest_digest, 0); - rb_define_method(cDigest, "hexdigest", ossl_digest_hexdigest, 0); - rb_define_alias(cDigest, "inspect", "hexdigest"); - rb_define_alias(cDigest, "to_s", "hexdigest"); - rb_define_method(cDigest, "update", ossl_digest_update, 1); rb_define_alias(cDigest, "<<", "update"); - - rb_define_method(cDigest, "==", ossl_digest_equal, 1); - + rb_define_private_method(cDigest, "finish", ossl_digest_finish, 0); + rb_define_method(cDigest, "digest_length", ossl_digest_size, 0); + rb_define_method(cDigest, "block_length", ossl_digest_block_length, 0); + rb_define_method(cDigest, "name", ossl_digest_name, 0); - rb_define_method(cDigest, "size", ossl_digest_size, 0); + + id_md_holder = rb_intern_const("EVP_MD_holder"); } |
