From 8253d7c9cea16c2aa009b59db4f5d93afb74c6eb Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Tue, 30 Jun 2020 14:27:13 +0900 Subject: [PATCH 1/2] hmac: add a test case for OpenSSL::HMAC singleton methods --- test/openssl/test_hmac.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/openssl/test_hmac.rb b/test/openssl/test_hmac.rb index 9cb3c5a86..7202a5902 100644 --- a/test/openssl/test_hmac.rb +++ b/test/openssl/test_hmac.rb @@ -49,6 +49,15 @@ def test_eq refute_equal h1, h2.digest refute_equal h1, h3 end + + def test_singleton_methods + # RFC 2202 2. Test Cases for HMAC-MD5 + key = ["0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"].pack("H*") + digest = OpenSSL::HMAC.digest("MD5", key, "Hi There") + assert_equal ["9294727a3638bb1c13f48ef8158bfc9d"].pack("H*"), digest + hexdigest = OpenSSL::HMAC.hexdigest("MD5", key, "Hi There") + assert_equal "9294727a3638bb1c13f48ef8158bfc9d", hexdigest + end end end From 0317e2fc028be40a7d64d0e4337d3e21539613ce Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 18 May 2020 16:15:07 +0900 Subject: [PATCH 2/2] hmac: migrate from the low-level HMAC API to the EVP API Use the EVP API instead of the low-level HMAC API. Use of the HMAC API has been discouraged and is being marked as deprecated starting from OpenSSL 3.0.0. The two singleton methods OpenSSL::HMAC, HMAC.digest and HMAC.hexdigest are now in lib/openssl/hmac.rb. --- ext/openssl/extconf.rb | 3 +- ext/openssl/lib/openssl/hmac.rb | 40 +++++++ ext/openssl/openssl_missing.c | 26 ----- ext/openssl/openssl_missing.h | 10 +- ext/openssl/ossl.h | 1 - ext/openssl/ossl_hmac.c | 179 ++++++++------------------------ 6 files changed, 89 insertions(+), 170 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 693e55cd9..063498a76 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -141,8 +141,7 @@ def find_openssl_library have_func("BN_GENCB_get_arg") have_func("EVP_MD_CTX_new") have_func("EVP_MD_CTX_free") -have_func("HMAC_CTX_new") -have_func("HMAC_CTX_free") +have_func("EVP_MD_CTX_pkey_ctx") have_func("X509_STORE_get_ex_data") have_func("X509_STORE_set_ex_data") have_func("X509_STORE_get_ex_new_index") diff --git a/ext/openssl/lib/openssl/hmac.rb b/ext/openssl/lib/openssl/hmac.rb index 3d4427611d..9bc8bc8df3 100644 --- a/ext/openssl/lib/openssl/hmac.rb +++ b/ext/openssl/lib/openssl/hmac.rb @@ -9,5 +9,45 @@ def ==(other) OpenSSL.fixed_length_secure_compare(self.digest, other.digest) end + + class << self + # :call-seq: + # HMAC.digest(digest, key, data) -> aString + # + # Returns the authentication code as a binary string. The _digest_ parameter + # specifies the digest algorithm to use. This may be a String representing + # the algorithm name or an instance of OpenSSL::Digest. + # + # === Example + # key = 'key' + # data = 'The quick brown fox jumps over the lazy dog' + # + # hmac = OpenSSL::HMAC.digest('SHA1', key, data) + # #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9" + def digest(digest, key, data) + hmac = new(key, digest) + hmac << data + hmac.digest + end + + # :call-seq: + # HMAC.hexdigest(digest, key, data) -> aString + # + # Returns the authentication code as a hex-encoded string. The _digest_ + # parameter specifies the digest algorithm to use. This may be a String + # representing the algorithm name or an instance of OpenSSL::Digest. + # + # === Example + # key = 'key' + # data = 'The quick brown fox jumps over the lazy dog' + # + # hmac = OpenSSL::HMAC.hexdigest('SHA1', key, data) + # #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" + def hexdigest(digest, key, data) + hmac = new(key, digest) + hmac << data + hmac.hexdigest + end + end end end diff --git a/ext/openssl/openssl_missing.c b/ext/openssl/openssl_missing.c index b36ef0288..010c158dc 100644 --- a/ext/openssl/openssl_missing.c +++ b/ext/openssl/openssl_missing.c @@ -13,9 +13,6 @@ #if !defined(OPENSSL_NO_ENGINE) # include #endif -#if !defined(OPENSSL_NO_HMAC) -# include -#endif #include #include "openssl_missing.h" @@ -58,29 +55,6 @@ ossl_EC_curve_nist2nid(const char *name) #endif /*** added in 1.1.0 ***/ -#if !defined(HAVE_HMAC_CTX_NEW) -HMAC_CTX * -ossl_HMAC_CTX_new(void) -{ - HMAC_CTX *ctx = OPENSSL_malloc(sizeof(HMAC_CTX)); - if (!ctx) - return NULL; - HMAC_CTX_init(ctx); - return ctx; -} -#endif - -#if !defined(HAVE_HMAC_CTX_FREE) -void -ossl_HMAC_CTX_free(HMAC_CTX *ctx) -{ - if (ctx) { - HMAC_CTX_cleanup(ctx); - OPENSSL_free(ctx); - } -} -#endif - #if !defined(HAVE_X509_CRL_GET0_SIGNATURE) void ossl_X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig, diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h index 7d218f86f..06d2a9082 100644 --- a/ext/openssl/openssl_missing.h +++ b/ext/openssl/openssl_missing.h @@ -54,14 +54,8 @@ int ossl_EC_curve_nist2nid(const char *); # define EVP_MD_CTX_free EVP_MD_CTX_destroy #endif -#if !defined(HAVE_HMAC_CTX_NEW) -HMAC_CTX *ossl_HMAC_CTX_new(void); -# define HMAC_CTX_new ossl_HMAC_CTX_new -#endif - -#if !defined(HAVE_HMAC_CTX_FREE) -void ossl_HMAC_CTX_free(HMAC_CTX *); -# define HMAC_CTX_free ossl_HMAC_CTX_free +#if !defined(HAVE_EVP_MD_CTX_PKEY_CTX) +# define EVP_MD_CTX_pkey_ctx(x) (x)->pctx #endif #if !defined(HAVE_X509_STORE_GET_EX_DATA) diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index c20f506bd..577eb6d6b 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #ifndef OPENSSL_NO_TS diff --git a/ext/openssl/ossl_hmac.c b/ext/openssl/ossl_hmac.c index 70e9fb819..a21db6c48 100644 --- a/ext/openssl/ossl_hmac.c +++ b/ext/openssl/ossl_hmac.c @@ -7,14 +7,12 @@ * This program is licensed under the same licence as Ruby. * (See the file 'LICENCE'.) */ -#if !defined(OPENSSL_NO_HMAC) - #include "ossl.h" #define NewHMAC(klass) \ TypedData_Wrap_Struct((klass), &ossl_hmac_type, 0) #define GetHMAC(obj, ctx) do { \ - TypedData_Get_Struct((obj), HMAC_CTX, &ossl_hmac_type, (ctx)); \ + TypedData_Get_Struct((obj), EVP_MD_CTX, &ossl_hmac_type, (ctx)); \ if (!(ctx)) { \ ossl_raise(rb_eRuntimeError, "HMAC wasn't initialized"); \ } \ @@ -36,7 +34,7 @@ VALUE eHMACError; static void ossl_hmac_free(void *ctx) { - HMAC_CTX_free(ctx); + EVP_MD_CTX_free(ctx); } static const rb_data_type_t ossl_hmac_type = { @@ -51,12 +49,12 @@ static VALUE ossl_hmac_alloc(VALUE klass) { VALUE obj; - HMAC_CTX *ctx; + EVP_MD_CTX *ctx; obj = NewHMAC(klass); - ctx = HMAC_CTX_new(); + ctx = EVP_MD_CTX_new(); if (!ctx) - ossl_raise(eHMACError, NULL); + ossl_raise(eHMACError, "EVP_MD_CTX"); RTYPEDDATA_DATA(obj) = ctx; return obj; @@ -76,8 +74,7 @@ ossl_hmac_alloc(VALUE klass) * === Example * * key = 'key' - * digest = OpenSSL::Digest.new('sha1') - * instance = OpenSSL::HMAC.new(key, digest) + * instance = OpenSSL::HMAC.new(key, 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance.class * #=> OpenSSL::HMAC @@ -86,7 +83,7 @@ ossl_hmac_alloc(VALUE klass) * * Two instances can be securely compared with #== in constant time: * - * other_instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * other_instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance == other_instance * #=> true @@ -95,12 +92,23 @@ ossl_hmac_alloc(VALUE klass) static VALUE ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) { - HMAC_CTX *ctx; + EVP_MD_CTX *ctx; + EVP_PKEY *pkey; - StringValue(key); GetHMAC(self, ctx); - HMAC_Init_ex(ctx, RSTRING_PTR(key), RSTRING_LENINT(key), - ossl_evp_get_digestbyname(digest), NULL); + StringValue(key); + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + (unsigned char *)RSTRING_PTR(key), + RSTRING_LENINT(key)); + if (!pkey) + ossl_raise(eHMACError, "EVP_PKEY_new_mac_key"); + if (EVP_DigestSignInit(ctx, NULL, ossl_evp_get_digestbyname(digest), + NULL, pkey) != 1) { + EVP_PKEY_free(pkey); + ossl_raise(eHMACError, "EVP_DigestSignInit"); + } + /* Decrement reference counter; EVP_MD_CTX still keeps it */ + EVP_PKEY_free(pkey); return self; } @@ -108,16 +116,15 @@ ossl_hmac_initialize(VALUE self, VALUE key, VALUE digest) static VALUE ossl_hmac_copy(VALUE self, VALUE other) { - HMAC_CTX *ctx1, *ctx2; + EVP_MD_CTX *ctx1, *ctx2; rb_check_frozen(self); if (self == other) return self; GetHMAC(self, ctx1); GetHMAC(other, ctx2); - - if (!HMAC_CTX_copy(ctx1, ctx2)) - ossl_raise(eHMACError, "HMAC_CTX_copy"); + if (EVP_MD_CTX_copy(ctx1, ctx2) != 1) + ossl_raise(eHMACError, "EVP_MD_CTX_copy"); return self; } @@ -142,33 +149,16 @@ ossl_hmac_copy(VALUE self, VALUE other) static VALUE ossl_hmac_update(VALUE self, VALUE data) { - HMAC_CTX *ctx; + EVP_MD_CTX *ctx; StringValue(data); GetHMAC(self, ctx); - HMAC_Update(ctx, (unsigned char *)RSTRING_PTR(data), RSTRING_LEN(data)); + if (EVP_DigestSignUpdate(ctx, RSTRING_PTR(data), RSTRING_LEN(data)) != 1) + ossl_raise(eHMACError, "EVP_DigestSignUpdate"); return self; } -static void -hmac_final(HMAC_CTX *ctx, unsigned char *buf, unsigned int *buf_len) -{ - HMAC_CTX *final; - - final = HMAC_CTX_new(); - if (!final) - ossl_raise(eHMACError, "HMAC_CTX_new"); - - if (!HMAC_CTX_copy(final, ctx)) { - HMAC_CTX_free(final); - ossl_raise(eHMACError, "HMAC_CTX_copy"); - } - - HMAC_Final(final, buf, buf_len); - HMAC_CTX_free(final); -} - /* * call-seq: * hmac.digest -> string @@ -176,7 +166,7 @@ hmac_final(HMAC_CTX *ctx, unsigned char *buf, unsigned int *buf_len) * Returns the authentication code an instance represents as a binary string. * * === Example - * instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * instance.digest * #=> "\xF4+\xB0\xEE\xB0\x18\xEB\xBDE\x97\xAEr\x13q\x1E\xC6\a`\x84?" @@ -184,15 +174,16 @@ hmac_final(HMAC_CTX *ctx, unsigned char *buf, unsigned int *buf_len) static VALUE ossl_hmac_digest(VALUE self) { - HMAC_CTX *ctx; - unsigned int buf_len; + EVP_MD_CTX *ctx; + size_t buf_len; VALUE ret; GetHMAC(self, ctx); ret = rb_str_new(NULL, EVP_MAX_MD_SIZE); - hmac_final(ctx, (unsigned char *)RSTRING_PTR(ret), &buf_len); - assert(buf_len <= EVP_MAX_MD_SIZE); - rb_str_set_len(ret, buf_len); + if (EVP_DigestSignFinal(ctx, (unsigned char *)RSTRING_PTR(ret), + &buf_len) != 1) + ossl_raise(eHMACError, "EVP_DigestSignFinal"); + rb_str_set_len(ret, (long)buf_len); return ret; } @@ -207,13 +198,14 @@ ossl_hmac_digest(VALUE self) static VALUE ossl_hmac_hexdigest(VALUE self) { - HMAC_CTX *ctx; + EVP_MD_CTX *ctx; unsigned char buf[EVP_MAX_MD_SIZE]; - unsigned int buf_len; + size_t buf_len; VALUE ret; GetHMAC(self, ctx); - hmac_final(ctx, buf, &buf_len); + if (EVP_DigestSignFinal(ctx, buf, &buf_len) != 1) + ossl_raise(eHMACError, "EVP_DigestSignFinal"); ret = rb_str_new(NULL, buf_len * 2); ossl_bin2hex(buf, RSTRING_PTR(ret), buf_len); @@ -230,7 +222,7 @@ ossl_hmac_hexdigest(VALUE self) * === Example * * data = "The quick brown fox jumps over the lazy dog" - * instance = OpenSSL::HMAC.new('key', OpenSSL::Digest.new('sha1')) + * instance = OpenSSL::HMAC.new('key', 'SHA1') * #=> f42bb0eeb018ebbd4597ae7213711ec60760843f * * instance.update(data) @@ -242,84 +234,17 @@ ossl_hmac_hexdigest(VALUE self) static VALUE ossl_hmac_reset(VALUE self) { - HMAC_CTX *ctx; + EVP_MD_CTX *ctx; + EVP_PKEY *pkey; GetHMAC(self, ctx); - HMAC_Init_ex(ctx, NULL, 0, NULL, NULL); + pkey = EVP_PKEY_CTX_get0_pkey(EVP_MD_CTX_pkey_ctx(ctx)); + if (EVP_DigestSignInit(ctx, NULL, EVP_MD_CTX_md(ctx), NULL, pkey) != 1) + ossl_raise(eHMACError, "EVP_DigestSignInit"); return self; } -/* - * call-seq: - * HMAC.digest(digest, key, data) -> aString - * - * Returns the authentication code as a binary string. The _digest_ parameter - * specifies the digest algorithm to use. This may be a String representing - * the algorithm name or an instance of OpenSSL::Digest. - * - * === Example - * - * key = 'key' - * data = 'The quick brown fox jumps over the lazy dog' - * - * hmac = OpenSSL::HMAC.digest('sha1', key, data) - * #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9" - * - */ -static VALUE -ossl_hmac_s_digest(VALUE klass, VALUE digest, VALUE key, VALUE data) -{ - unsigned char *buf; - unsigned int buf_len; - - StringValue(key); - StringValue(data); - buf = HMAC(ossl_evp_get_digestbyname(digest), RSTRING_PTR(key), - RSTRING_LENINT(key), (unsigned char *)RSTRING_PTR(data), - RSTRING_LEN(data), NULL, &buf_len); - - return rb_str_new((const char *)buf, buf_len); -} - -/* - * call-seq: - * HMAC.hexdigest(digest, key, data) -> aString - * - * Returns the authentication code as a hex-encoded string. The _digest_ - * parameter specifies the digest algorithm to use. This may be a String - * representing the algorithm name or an instance of OpenSSL::Digest. - * - * === Example - * - * key = 'key' - * data = 'The quick brown fox jumps over the lazy dog' - * - * hmac = OpenSSL::HMAC.hexdigest('sha1', key, data) - * #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9" - * - */ -static VALUE -ossl_hmac_s_hexdigest(VALUE klass, VALUE digest, VALUE key, VALUE data) -{ - unsigned char buf[EVP_MAX_MD_SIZE]; - unsigned int buf_len; - VALUE ret; - - StringValue(key); - StringValue(data); - - if (!HMAC(ossl_evp_get_digestbyname(digest), RSTRING_PTR(key), - RSTRING_LENINT(key), (unsigned char *)RSTRING_PTR(data), - RSTRING_LEN(data), buf, &buf_len)) - ossl_raise(eHMACError, "HMAC"); - - ret = rb_str_new(NULL, buf_len * 2); - ossl_bin2hex(buf, RSTRING_PTR(ret), buf_len); - - return ret; -} - /* * INIT */ @@ -353,8 +278,7 @@ Init_ossl_hmac(void) * data1 = File.read("file1") * data2 = File.read("file2") * key = "key" - * digest = OpenSSL::Digest.new('SHA256') - * hmac = OpenSSL::HMAC.new(key, digest) + * hmac = OpenSSL::HMAC.new(key, 'SHA256') * hmac << data1 * hmac << data2 * mac = hmac.digest @@ -364,8 +288,6 @@ Init_ossl_hmac(void) cHMAC = rb_define_class_under(mOSSL, "HMAC", rb_cObject); rb_define_alloc_func(cHMAC, ossl_hmac_alloc); - rb_define_singleton_method(cHMAC, "digest", ossl_hmac_s_digest, 3); - rb_define_singleton_method(cHMAC, "hexdigest", ossl_hmac_s_hexdigest, 3); rb_define_method(cHMAC, "initialize", ossl_hmac_initialize, 2); rb_define_method(cHMAC, "initialize_copy", ossl_hmac_copy, 1); @@ -378,12 +300,3 @@ Init_ossl_hmac(void) rb_define_alias(cHMAC, "inspect", "hexdigest"); rb_define_alias(cHMAC, "to_s", "hexdigest"); } - -#else /* NO_HMAC */ -# warning >>> OpenSSL is compiled without HMAC support <<< -void -Init_ossl_hmac(void) -{ - rb_warning("HMAC is not available: OpenSSL is compiled without HMAC."); -} -#endif /* NO_HMAC */ -- 2.34.1