From 2800057a34b421872c7ad6efbf496dd8f40fc2ae Mon Sep 17 00:00:00 2001 From: Koichi Fukuda <64008381+kmfukuda@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:49:22 +0000 Subject: [PATCH 1/5] cmac: add an implementation of OpenSSL::CMAC that uses CMAC_*() The functions are available in each supported version of OpenSSL and LibreSSL, though deprecated in OpenSSL 3.0. --- ext/openssl/ossl.c | 1 + ext/openssl/ossl.h | 2 + ext/openssl/ossl_cmac.c | 139 ++++++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_cmac.h | 9 +++ lib/openssl.rb | 1 + lib/openssl/cmac.rb | 42 ++++++++++++ 6 files changed, 194 insertions(+) create mode 100644 ext/openssl/ossl_cmac.c create mode 100644 ext/openssl/ossl_cmac.h create mode 100644 lib/openssl/cmac.rb diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 59ad7d19a..94089d748 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1155,6 +1155,7 @@ Init_openssl(void) Init_ossl_config(); Init_ossl_digest(); Init_ossl_hmac(); + Init_ossl_cmac(); Init_ossl_ns_spki(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index c3140ac3e..1a93e09b3 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -39,6 +39,7 @@ #include #include #include +#include #ifndef LIBRESSL_VERSION_NUMBER # define OSSL_IS_LIBRESSL 0 @@ -180,6 +181,7 @@ extern VALUE dOSSL; #include "ossl_config.h" #include "ossl_digest.h" #include "ossl_hmac.h" +#include "ossl_cmac.h" #include "ossl_ns_spki.h" #include "ossl_ocsp.h" #include "ossl_pkcs12.h" diff --git a/ext/openssl/ossl_cmac.c b/ext/openssl/ossl_cmac.c new file mode 100644 index 000000000..0a6e8d52d --- /dev/null +++ b/ext/openssl/ossl_cmac.c @@ -0,0 +1,139 @@ +#include "ossl.h" + +#define NewCMAC(klass) \ + TypedData_Wrap_Struct((klass), &ossl_cmac_type, 0) +#define SetCMAC(obj, ctx) do { \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "CMAC wasn't initialized"); \ + RTYPEDDATA_DATA(obj) = (ctx); \ + } while (0) +#define GetCMAC(obj, ctx) do { \ + TypedData_Get_Struct((obj), CMAC_CTX, &ossl_cmac_type, (ctx)); \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "CMAC wasn't initialized"); \ + } while (0) + +/* + * Classes + */ +VALUE cCMAC; +VALUE eCMACError; + +/* + * Public + */ + +/* + * Private + */ +static void +ossl_cmac_free(void *ctx) +{ + CMAC_CTX_free(ctx); +} + +static const rb_data_type_t ossl_cmac_type = { + "OpenSSL/CMAC", + { + 0, ossl_cmac_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +ossl_cmac_alloc(VALUE klass) +{ + VALUE obj; + CMAC_CTX *ctx; + + obj = NewCMAC(klass); + ctx = CMAC_CTX_new(); + if (!ctx) + ossl_raise(eCMACError, "CMAC_CTX_new"); + SetCMAC(obj, ctx); + + return obj; +} + +static VALUE +ossl_cmac_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE vkey, vcipher; + CMAC_CTX *ctx; + const EVP_CIPHER *cipher; + + rb_scan_args(argc, argv, "11", &vkey, &vcipher); + + GetCMAC(self, ctx); + StringValue(vkey); + cipher = NIL_P(vcipher) ? EVP_aes_128_cbc() : ossl_evp_get_cipherbyname(vcipher); + if (CMAC_Init(ctx, RSTRING_PTR(vkey), RSTRING_LEN(vkey), cipher, NULL) != 1) + ossl_raise(eCMACError, "CMAC_Init"); + + return self; +} + +static VALUE +ossl_cmac_copy(VALUE self, VALUE other) +{ + CMAC_CTX *ctx1, *ctx2; + + rb_check_frozen(self); + if (self == other) + return self; + + GetCMAC(self, ctx1); + GetCMAC(other, ctx2); + if (CMAC_CTX_copy(ctx1, ctx2) != 1) + ossl_raise(eCMACError, "CMAC_CTX_copy"); + + return self; +} + +static VALUE +ossl_cmac_update(VALUE self, VALUE chunk) +{ + CMAC_CTX *ctx; + + GetCMAC(self, ctx); + StringValue(chunk); + if (CMAC_Update(ctx, RSTRING_PTR(chunk), RSTRING_LEN(chunk)) != 1) + ossl_raise(eCMACError, "CMAC_Update"); + + return self; +} + +static VALUE +ossl_cmac_mac(VALUE self) +{ + VALUE ret; + CMAC_CTX *ctx; + size_t len; + + GetCMAC(self, ctx); + if (CMAC_Final(ctx, NULL, &len) != 1) + ossl_raise(eCMACError, "CMAC_Final"); + ret = rb_str_new(NULL, len); + if (CMAC_Final(ctx, (unsigned char *)RSTRING_PTR(ret), &len) != 1) + ossl_raise(eCMACError, "CMAC_Final"); + if (CMAC_resume(ctx) != 1) + ossl_raise(eCMACError, "CMAC_resume"); + + return ret; +} + +/* + * INIT + */ +void +Init_ossl_cmac(void) +{ + cCMAC = rb_define_class_under(mOSSL, "CMAC", rb_cObject); + eCMACError = rb_define_class_under(mOSSL, "CMACError", eOSSLError); + rb_define_alloc_func(cCMAC, ossl_cmac_alloc); + rb_define_method(cCMAC, "initialize", ossl_cmac_initialize, -1); + rb_define_method(cCMAC, "initialize_copy", ossl_cmac_copy, 1); + rb_define_method(cCMAC, "update", ossl_cmac_update, 1); + rb_define_alias(cCMAC, "<<", "update"); + rb_define_method(cCMAC, "mac", ossl_cmac_mac, 0); +} diff --git a/ext/openssl/ossl_cmac.h b/ext/openssl/ossl_cmac.h new file mode 100644 index 000000000..5e57aa5d9 --- /dev/null +++ b/ext/openssl/ossl_cmac.h @@ -0,0 +1,9 @@ +#if !defined(_OSSL_CMAC_H_) +#define _OSSL_CMAC_H_ + +extern VALUE cCMAC; +extern VALUE eCMACError; + +void Init_ossl_cmac(void); + +#endif /* _OSSL_CMAC_H_ */ diff --git a/lib/openssl.rb b/lib/openssl.rb index 088992389..ea85e0808 100644 --- a/lib/openssl.rb +++ b/lib/openssl.rb @@ -18,6 +18,7 @@ require_relative 'openssl/cipher' require_relative 'openssl/digest' require_relative 'openssl/hmac' +require_relative 'openssl/cmac' require_relative 'openssl/x509' require_relative 'openssl/ssl' require_relative 'openssl/pkcs5' diff --git a/lib/openssl/cmac.rb b/lib/openssl/cmac.rb new file mode 100644 index 000000000..cfebc0509 --- /dev/null +++ b/lib/openssl/cmac.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module OpenSSL + class CMAC + def ==(other) + return false unless CMAC === other + return false unless self.mac.bytesize == other.mac.bytesize + + OpenSSL.fixed_length_secure_compare(self.mac, other.mac) + end + + def hexmac + mac.unpack1('H*') + end + alias inspect hexmac + alias to_s hexmac + + def base64mac + [mac].pack('m0') + end + + class << self + def mac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.mac + end + + def hexmac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.hexmac + end + + def base64mac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.base64mac + end + end + end +end From 29cb8d1c7fda609c3e1cc1e345b2fd0b7c52dad3 Mon Sep 17 00:00:00 2001 From: Koichi Fukuda <64008381+kmfukuda@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:50:44 +0000 Subject: [PATCH 2/5] cmac: add an implementation of OpenSSL::CMAC that uses EVP_MAC_*() The functions are suggested in OpenSSL 3.0, though available only in that version. --- ext/openssl/ossl.c | 1 + ext/openssl/ossl.h | 1 + ext/openssl/ossl_cmac.c | 159 ++++++++++++++++++++++++++++++++++++++++ ext/openssl/ossl_cmac.h | 9 +++ lib/openssl.rb | 1 + lib/openssl/cmac.rb | 42 +++++++++++ 6 files changed, 213 insertions(+) create mode 100644 ext/openssl/ossl_cmac.c create mode 100644 ext/openssl/ossl_cmac.h create mode 100644 lib/openssl/cmac.rb diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 59ad7d19a..94089d748 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1155,6 +1155,7 @@ Init_openssl(void) Init_ossl_config(); Init_ossl_digest(); Init_ossl_hmac(); + Init_ossl_cmac(); Init_ossl_ns_spki(); Init_ossl_pkcs12(); Init_ossl_pkcs7(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index c3140ac3e..4bd8154bf 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -180,6 +180,7 @@ extern VALUE dOSSL; #include "ossl_config.h" #include "ossl_digest.h" #include "ossl_hmac.h" +#include "ossl_cmac.h" #include "ossl_ns_spki.h" #include "ossl_ocsp.h" #include "ossl_pkcs12.h" diff --git a/ext/openssl/ossl_cmac.c b/ext/openssl/ossl_cmac.c new file mode 100644 index 000000000..e9fe62d33 --- /dev/null +++ b/ext/openssl/ossl_cmac.c @@ -0,0 +1,159 @@ +#include "ossl.h" + +#define NewCMAC(klass) \ + TypedData_Wrap_Struct((klass), &ossl_cmac_type, 0) +#define SetCMAC(obj, ctx) do { \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "CMAC wasn't initialized"); \ + RTYPEDDATA_DATA(obj) = (ctx); \ + } while (0) +#define GetCMAC(obj, ctx) do { \ + TypedData_Get_Struct((obj), EVP_MAC_CTX, &ossl_cmac_type, (ctx)); \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "CMAC wasn't initialized"); \ + } while (0) + +/* + * Classes + */ +VALUE cCMAC; +VALUE eCMACError; + +/* + * Public + */ + +/* + * Private + */ +static void +ossl_cmac_free(void *ctx) +{ + EVP_MAC_CTX_free(ctx); +} + +static const rb_data_type_t ossl_cmac_type = { + "OpenSSL/CMAC", + { + 0, ossl_cmac_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +ossl_cmac_alloc(VALUE klass) +{ + VALUE obj; + EVP_MAC *mac; + EVP_MAC_CTX *ctx; + + obj = NewCMAC(klass); + mac = EVP_MAC_fetch(NULL, "CMAC", NULL); + if (!mac) + ossl_raise(eCMACError, "EVP_MAC_fetch"); + ctx = EVP_MAC_CTX_new(mac); + if (!ctx) { + EVP_MAC_free(mac); + ossl_raise(eCMACError, "EVP_MAC_CTX_new"); + } + SetCMAC(obj, ctx); + + return obj; +} + +static VALUE +ossl_cmac_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE vkey, vcipher; + EVP_MAC_CTX *ctx; + OSSL_PARAM params[] = { + OSSL_PARAM_END, + OSSL_PARAM_END, + }; + + rb_scan_args(argc, argv, "11", &vkey, &vcipher); + + GetCMAC(self, ctx); + StringValue(vkey); + if (NIL_P(vcipher)) + vcipher = rb_str_new_cstr("AES-128-CBC"); + else if (rb_obj_is_kind_of(vcipher, cCipher)) + vcipher = rb_funcall(vcipher, rb_intern("name"), 0); + params[0] = OSSL_PARAM_construct_utf8_string("cipher", StringValueCStr(vcipher), 0); + if (EVP_MAC_init(ctx, (unsigned char *)RSTRING_PTR(vkey), RSTRING_LEN(vkey), params) != 1) + ossl_raise(eCMACError, "EVP_MAC_init"); + + return self; +} + +static VALUE +ossl_cmac_copy(VALUE self, VALUE other) +{ + EVP_MAC_CTX *ctx1, *ctx2, *ctx3; + + rb_check_frozen(self); + if (self == other) + return self; + + GetCMAC(self, ctx1); + GetCMAC(other, ctx2); + ctx3 = EVP_MAC_CTX_dup(ctx2); + if (!ctx3) + ossl_raise(eCMACError, "EVP_MAC_CTX_dup"); + SetCMAC(self, ctx3); + EVP_MAC_CTX_free(ctx1); + + return self; +} + +static VALUE +ossl_cmac_update(VALUE self, VALUE chunk) +{ + EVP_MAC_CTX *ctx; + + GetCMAC(self, ctx); + StringValue(chunk); + if (EVP_MAC_update(ctx, (unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk)) != 1) + ossl_raise(eCMACError, "EVP_MAC_update"); + + return self; +} + +static VALUE +ossl_cmac_mac(VALUE self) +{ + VALUE ret; + EVP_MAC_CTX *ctx1, *ctx2; + size_t len; + + GetCMAC(self, ctx1); + if (EVP_MAC_final(ctx1, NULL, &len, 0) != 1) + ossl_raise(eCMACError, "EVP_MAC_final"); + ret = rb_str_new(NULL, len); + ctx2 = EVP_MAC_CTX_dup(ctx1); + if (!ctx2) + ossl_raise(eCMACError, "EVP_MAC_CTX_dup"); + if (EVP_MAC_final(ctx2, (unsigned char *)RSTRING_PTR(ret), &len, RSTRING_LEN(ret)) != 1) { + EVP_MAC_CTX_free(ctx2); + ossl_raise(eCMACError, "EVP_MAC_final"); + } + EVP_MAC_CTX_free(ctx2); + + return ret; +} + +/* + * INIT + */ +void +Init_ossl_cmac(void) +{ + cCMAC = rb_define_class_under(mOSSL, "CMAC", rb_cObject); + eCMACError = rb_define_class_under(mOSSL, "CMACError", eOSSLError); + rb_define_alloc_func(cCMAC, ossl_cmac_alloc); + rb_define_method(cCMAC, "initialize", ossl_cmac_initialize, -1); + rb_define_method(cCMAC, "initialize_copy", ossl_cmac_copy, 1); + rb_define_method(cCMAC, "update", ossl_cmac_update, 1); + rb_define_alias(cCMAC, "<<", "update"); + rb_define_method(cCMAC, "mac", ossl_cmac_mac, 0); +} diff --git a/ext/openssl/ossl_cmac.h b/ext/openssl/ossl_cmac.h new file mode 100644 index 000000000..5e57aa5d9 --- /dev/null +++ b/ext/openssl/ossl_cmac.h @@ -0,0 +1,9 @@ +#if !defined(_OSSL_CMAC_H_) +#define _OSSL_CMAC_H_ + +extern VALUE cCMAC; +extern VALUE eCMACError; + +void Init_ossl_cmac(void); + +#endif /* _OSSL_CMAC_H_ */ diff --git a/lib/openssl.rb b/lib/openssl.rb index 088992389..ea85e0808 100644 --- a/lib/openssl.rb +++ b/lib/openssl.rb @@ -18,6 +18,7 @@ require_relative 'openssl/cipher' require_relative 'openssl/digest' require_relative 'openssl/hmac' +require_relative 'openssl/cmac' require_relative 'openssl/x509' require_relative 'openssl/ssl' require_relative 'openssl/pkcs5' diff --git a/lib/openssl/cmac.rb b/lib/openssl/cmac.rb new file mode 100644 index 000000000..cfebc0509 --- /dev/null +++ b/lib/openssl/cmac.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module OpenSSL + class CMAC + def ==(other) + return false unless CMAC === other + return false unless self.mac.bytesize == other.mac.bytesize + + OpenSSL.fixed_length_secure_compare(self.mac, other.mac) + end + + def hexmac + mac.unpack1('H*') + end + alias inspect hexmac + alias to_s hexmac + + def base64mac + [mac].pack('m0') + end + + class << self + def mac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.mac + end + + def hexmac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.hexmac + end + + def base64mac(key, message, cipher = nil) + cmac = new(key, cipher) + cmac << message + cmac.base64mac + end + end + end +end From 2002791649de294829d4c6c501191b0bc16e2eb4 Mon Sep 17 00:00:00 2001 From: Koichi Fukuda <64008381+kmfukuda@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:55:12 +0000 Subject: [PATCH 3/5] cmac: add tests for OpenSSL::CMAC --- test/openssl/test_cmac.rb | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/openssl/test_cmac.rb diff --git a/test/openssl/test_cmac.rb b/test/openssl/test_cmac.rb new file mode 100644 index 000000000..9b4ad67f9 --- /dev/null +++ b/test/openssl/test_cmac.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require_relative "utils" + +if defined?(OpenSSL) + +class OpenSSL::TestCMAC < OpenSSL::TestCase + def test_cmac + cmac = OpenSSL::CMAC.new(["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")) + cmac.update(["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal ["070a16b46b4d4144f79bdd9dd04a287c"].pack("H*"), cmac.mac + assert_equal "070a16b46b4d4144f79bdd9dd04a287c", cmac.hexmac + assert_equal "BwoWtGtNQUT3m92d0EoofA==", cmac.base64mac + + cmac = OpenSSL::CMAC.new(["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].pack("H*"), "AES-192-CBC") + cmac.update(["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal ["9e99a7bf31e710900662f65e617c5184"].pack("H*"), cmac.mac + assert_equal "9e99a7bf31e710900662f65e617c5184", cmac.hexmac + assert_equal "npmnvzHnEJAGYvZeYXxRhA==", cmac.base64mac + + cmac = OpenSSL::CMAC.new(["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), OpenSSL::Cipher.new("AES-256-CBC")) + cmac.update(["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal ["28a7023f452e8f82bd4bf28d8c37c35c"].pack("H*"), cmac.mac + assert_equal "28a7023f452e8f82bd4bf28d8c37c35c", cmac.hexmac + assert_equal "KKcCP0Uuj4K9S/KNjDfDXA==", cmac.base64mac + end + + def test_dup + cmac1 = OpenSSL::CMAC.new(["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*")) + cmac2 = cmac1.dup + assert_equal cmac2, cmac1 + + cmac1.update("message") + assert_not_equal cmac2, cmac1 + + cmac2.update("message") + assert_equal cmac2, cmac1 + end + + def test_class_methods + assert_equal ["070a16b46b4d4144f79bdd9dd04a287c"].pack("H*"), OpenSSL::CMAC.mac(["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal "070a16b46b4d4144f79bdd9dd04a287c", OpenSSL::CMAC.hexmac(["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal "BwoWtGtNQUT3m92d0EoofA==", OpenSSL::CMAC.base64mac(["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + + assert_equal ["9e99a7bf31e710900662f65e617c5184"].pack("H*"), OpenSSL::CMAC.mac(["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), "AES-192-CBC") + assert_equal "9e99a7bf31e710900662f65e617c5184", OpenSSL::CMAC.hexmac(["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), "AES-192-CBC") + assert_equal "npmnvzHnEJAGYvZeYXxRhA==", OpenSSL::CMAC.base64mac(["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), "AES-192-CBC") + + assert_equal ["28a7023f452e8f82bd4bf28d8c37c35c"].pack("H*"), OpenSSL::CMAC.mac(["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), OpenSSL::Cipher.new("AES-256-CBC")) + assert_equal "28a7023f452e8f82bd4bf28d8c37c35c", OpenSSL::CMAC.hexmac(["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), OpenSSL::Cipher.new("AES-256-CBC")) + assert_equal "KKcCP0Uuj4K9S/KNjDfDXA==", OpenSSL::CMAC.base64mac(["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*"), OpenSSL::Cipher.new("AES-256-CBC")) + end +end + +end From 63e7e5f6d662276264665d3f06e528aed70055e7 Mon Sep 17 00:00:00 2001 From: Koichi Fukuda <64008381+kmfukuda@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:56:50 +0000 Subject: [PATCH 4/5] cmac: add comments for OpenSSL::CMAC --- ext/openssl/ossl_cmac.c | 62 +++++++++++++++++++++++++++++++++++++++++ lib/openssl/cmac.rb | 44 +++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/ext/openssl/ossl_cmac.c b/ext/openssl/ossl_cmac.c index f2ab6c60e..b0ae0bbaf 100644 --- a/ext/openssl/ossl_cmac.c +++ b/ext/openssl/ossl_cmac.c @@ -86,6 +86,12 @@ ossl_cmac_alloc(VALUE klass) #endif } +/* + * call-seq: + * CMAC.new(key, cipher = "AES-128-CBC") -> new_cmac + * + * Returns an OpenSSL::CMAC for a message. + */ static VALUE ossl_cmac_initialize(int argc, VALUE *argv, VALUE self) { @@ -127,6 +133,7 @@ ossl_cmac_initialize(int argc, VALUE *argv, VALUE self) #endif } +/* :nodoc: */ static VALUE ossl_cmac_copy(VALUE self, VALUE other) { @@ -162,6 +169,12 @@ ossl_cmac_copy(VALUE self, VALUE other) #endif } +/* + * call-seq: + * update(chunk) -> self + * + * Updates +self+ with a chunk of the message. + */ static VALUE ossl_cmac_update(VALUE self, VALUE chunk) { @@ -186,6 +199,12 @@ ossl_cmac_update(VALUE self, VALUE chunk) #endif } +/* + * call-seq: + * mac -> string + * + * Returns the MAC. + */ static VALUE ossl_cmac_mac(VALUE self) { @@ -232,8 +251,51 @@ ossl_cmac_mac(VALUE self) void Init_ossl_cmac(void) { +#if 0 + mOSSL = rb_define_module("OpenSSL"); + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); +#endif + + /* + * Document-class: OpenSSL::CMAC + * + * OpenSSL::CMAC provides generation of \CMAC message authentication codes (MACs). + * The cipher algorithm to be used can be specified as a String or an OpenSSL::Cipher. + * OpenSSL::CMAC has a similar interface to OpenSSL::HMAC. + * + * === Examples + * + * key = ["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].pack("H*") + * message = ["6bc1bee22e409f96e93d7e117393172a"].pack("H*") + * + * OpenSSL::CMAC.mac(key, message, "AES-192-CBC") + * #=> "\x9E\x99\xA7\xBF1\xE7\x10\x90\x06b\xF6^a|Q\x84" + * OpenSSL::CMAC.hexmac(key, message, "AES-192-CBC") + * #=> "9e99a7bf31e710900662f65e617c5184" + * OpenSSL::CMAC.base64mac(key, message, "AES-192-CBC") + * #=> "npmnvzHnEJAGYvZeYXxRhA==" + * + * cmac = OpenSSL::CMAC.new(key, OpenSSL::Cipher.new("AES-192-CBC")) + * cmac.update(message[..11]) + * cmac.update(message[12..]) + * cmac.mac + * #=> "\x9E\x99\xA7\xBF1\xE7\x10\x90\x06b\xF6^a|Q\x84" + * cmac.hexmac + * #=> "9e99a7bf31e710900662f65e617c5184" + * cmac.base64mac + * #=> "npmnvzHnEJAGYvZeYXxRhA==" + * + */ cCMAC = rb_define_class_under(mOSSL, "CMAC", rb_cObject); + + /* + * Document-class: OpenSSL::CMACError + * + * Raised when a \CMAC operation fails. + * See OpenSSL::CMAC. + */ eCMACError = rb_define_class_under(mOSSL, "CMACError", eOSSLError); + rb_define_alloc_func(cCMAC, ossl_cmac_alloc); rb_define_method(cCMAC, "initialize", ossl_cmac_initialize, -1); rb_define_method(cCMAC, "initialize_copy", ossl_cmac_copy, 1); diff --git a/lib/openssl/cmac.rb b/lib/openssl/cmac.rb index cfebc0509..3d6ba13cf 100644 --- a/lib/openssl/cmac.rb +++ b/lib/openssl/cmac.rb @@ -2,6 +2,30 @@ module OpenSSL class CMAC + # :call-seq: + # == other -> true or false + # + # Returns +true+ if the other OpenSSL::CMAC has the same MAC as +self+; +false+ otherwise. + # + # This method compares two MACs of equal length in constant time. + # + # === Example + # + # key = ["2b7e151628aed2a6abf7158809cf4f3c"].pack("H*") + # message = "message" + # + # cmac1 = OpenSSL::CMAC.new(key) + # cmac2 = cmac1.dup + # cmac2 == cmac1 + # #=> true + # + # cmac1.update(message) + # cmac2 == cmac1 + # #=> false + # + # cmac2.update(message) + # cmac2 == cmac1 + # #=> true def ==(other) return false unless CMAC === other return false unless self.mac.bytesize == other.mac.bytesize @@ -9,29 +33,49 @@ def ==(other) OpenSSL.fixed_length_secure_compare(self.mac, other.mac) end + # :call-seq: + # hexmac -> string + # + # Returns the MAC as a hex string. def hexmac mac.unpack1('H*') end alias inspect hexmac alias to_s hexmac + # :call-seq: + # base64mac -> string + # + # Returns the MAC as a base 64 string. def base64mac [mac].pack('m0') end class << self + # :call-seq: + # CMAC.mac(key, message, cipher = "AES-128-CBC") -> string + # + # Returns the MAC. def mac(key, message, cipher = nil) cmac = new(key, cipher) cmac << message cmac.mac end + # :call-seq: + # CMAC.hexmac(key, message, cipher = "AES-128-CBC") -> string + # + # Returns the MAC as a hex string. def hexmac(key, message, cipher = nil) cmac = new(key, cipher) cmac << message cmac.hexmac end + # :call-seq: + # CMAC.base64mac(key, message, cipher = "AES-128-CBC") -> string + # + # Returns the MAC as a base 64 string. def base64mac(key, message, cipher = nil) cmac = new(key, cipher) cmac << message From 741eabfcaee6141b71ec47a2a3809279ec4410f8 Mon Sep 17 00:00:00 2001 From: Koichi Fukuda <64008381+kmfukuda@users.noreply.github.com> Date: Tue, 17 Sep 2024 03:58:11 +0000 Subject: [PATCH 5/5] cmac: avoid using CMAC_resume() The function was removed in LibreSSL 3.9. --- ext/openssl/ossl_cmac.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl_cmac.c b/ext/openssl/ossl_cmac.c index b0ae0bbaf..3f3c5582b 100644 --- a/ext/openssl/ossl_cmac.c +++ b/ext/openssl/ossl_cmac.c @@ -229,17 +229,25 @@ ossl_cmac_mac(VALUE self) return ret; #else VALUE ret; - CMAC_CTX *ctx; + CMAC_CTX *ctx1, *ctx2; size_t len; - GetCMAC(self, ctx); - if (CMAC_Final(ctx, NULL, &len) != 1) + GetCMAC(self, ctx1); + if (CMAC_Final(ctx1, NULL, &len) != 1) ossl_raise(eCMACError, "CMAC_Final"); ret = rb_str_new(NULL, len); - if (CMAC_Final(ctx, (unsigned char *)RSTRING_PTR(ret), &len) != 1) + ctx2 = CMAC_CTX_new(); + if (!ctx2) + ossl_raise(eCMACError, "CMAC_CTX_new"); + if (CMAC_CTX_copy(ctx2, ctx1) != 1) { + CMAC_CTX_free(ctx2); + ossl_raise(eCMACError, "CMAC_CTX_copy"); + } + if (CMAC_Final(ctx2, (unsigned char *)RSTRING_PTR(ret), &len) != 1) { + CMAC_CTX_free(ctx2); ossl_raise(eCMACError, "CMAC_Final"); - if (CMAC_resume(ctx) != 1) - ossl_raise(eCMACError, "CMAC_resume"); + } + CMAC_CTX_free(ctx2); return ret; #endif