diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 60780790b..0772e963b 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1040,6 +1040,7 @@ Init_openssl(void) Init_ossl_engine(); Init_ossl_hmac(); Init_ossl_kdf(); + Init_ossl_mac(); Init_ossl_ns_spki(); Init_ossl_ocsp(); Init_ossl_pkcs12(); diff --git a/ext/openssl/ossl.h b/ext/openssl/ossl.h index 9b20829b3..855e2c483 100644 --- a/ext/openssl/ossl.h +++ b/ext/openssl/ossl.h @@ -187,6 +187,7 @@ extern VALUE dOSSL; #include "ossl_digest.h" #include "ossl_engine.h" #include "ossl_hmac.h" +#include "ossl_mac.h" #include "ossl_kdf.h" #include "ossl_ns_spki.h" #include "ossl_ocsp.h" diff --git a/ext/openssl/ossl_mac.c b/ext/openssl/ossl_mac.c new file mode 100644 index 000000000..7cdfbb903 --- /dev/null +++ b/ext/openssl/ossl_mac.c @@ -0,0 +1,174 @@ +#include "ossl.h" + +#if OSSL_OPENSSL_PREREQ(3, 0, 0) + +#define NewMAC(klass) \ + TypedData_Wrap_Struct((klass), &ossl_mac_type, 0) +#define SetMAC(obj, ctx) do { \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "MAC wasn't initialized"); \ + RTYPEDDATA_DATA(obj) = (ctx); \ + } while (0) +#define GetMAC(obj, ctx) do { \ + TypedData_Get_Struct((obj), EVP_MAC_CTX, &ossl_mac_type, (ctx)); \ + if (!(ctx)) \ + ossl_raise(rb_eRuntimeError, "MAC wasn't initialized"); \ + } while (0) + +/* + * Classes + */ +static VALUE cMAC; +static VALUE cCMAC; +static VALUE eMACError; + +/* + * Public + */ + +/* + * Private + */ +static void +ossl_mac_free(void *ctx) +{ + EVP_MAC_CTX_free(ctx); +} + +static const rb_data_type_t ossl_mac_type = { + "OpenSSL/MAC", + { + 0, ossl_mac_free, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, +}; + +static VALUE +ossl_mac_alloc(VALUE klass) +{ + return NewMAC(klass); +} + +static VALUE +ossl_mac_initialize(VALUE self, VALUE algorithm) +{ + EVP_MAC *mac; + EVP_MAC_CTX *ctx; + + mac = EVP_MAC_fetch(NULL, StringValueCStr(algorithm), NULL); + if (!mac) + ossl_raise(eMACError, "EVP_MAC_fetch"); + ctx = EVP_MAC_CTX_new(mac); + if (!ctx) { + EVP_MAC_free(mac); + ossl_raise(eMACError, "EVP_MAC_CTX_new"); + } + SetMAC(self, ctx); + + return self; +} + +static VALUE +ossl_cmac_initialize(VALUE self, VALUE cipher, VALUE key) +{ + EVP_MAC_CTX *ctx; + VALUE algorithm; + OSSL_PARAM params[2]; + + algorithm = rb_str_new_literal("CMAC"); + rb_call_super(1, &algorithm); + + GetMAC(self, ctx); + StringValue(key); + params[0] = OSSL_PARAM_construct_utf8_string("cipher", StringValueCStr(cipher), 0); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_MAC_init(ctx, (unsigned char *)RSTRING_PTR(key), RSTRING_LEN(key), params) != 1) + ossl_raise(eMACError, "EVP_MAC_init"); + + return self; +} + +static VALUE +ossl_mac_copy(VALUE self, VALUE other) +{ + EVP_MAC_CTX *ctx1, *ctx2; + + rb_check_frozen(self); + if (self == other) + return self; + + GetMAC(other, ctx1); + ctx2 = EVP_MAC_CTX_dup(ctx1); + if (!ctx2) + ossl_raise(eMACError, "EVP_MAC_CTX_dup"); + SetMAC(self, ctx2); + + return self; +} + +static VALUE +ossl_mac_update(VALUE self, VALUE chunk) +{ + EVP_MAC_CTX *ctx; + + GetMAC(self, ctx); + StringValue(chunk); + if (EVP_MAC_update(ctx, (unsigned char *)RSTRING_PTR(chunk), RSTRING_LEN(chunk)) != 1) + ossl_raise(eMACError, "EVP_MAC_update"); + + return self; +} + +static VALUE +ossl_mac_mac(VALUE self) +{ + VALUE ret; + EVP_MAC_CTX *ctx1, *ctx2; + size_t len; + + GetMAC(self, ctx1); + if (EVP_MAC_final(ctx1, NULL, &len, 0) != 1) + ossl_raise(eMACError, "EVP_MAC_final"); + ret = rb_str_new(NULL, len); + ctx2 = EVP_MAC_CTX_dup(ctx1); + if (!ctx2) + ossl_raise(eMACError, "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(eMACError, "EVP_MAC_final"); + } + EVP_MAC_CTX_free(ctx2); + + return ret; +} + +/* + * INIT + */ +void +Init_ossl_mac(void) +{ +#if 0 + mOSSL = rb_define_module("OpenSSL"); + eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError); +#endif + + cMAC = rb_define_class_under(mOSSL, "MAC", rb_cObject); + rb_define_alloc_func(cMAC, ossl_mac_alloc); + rb_define_method(cMAC, "initialize", ossl_mac_initialize, 1); + rb_define_method(cMAC, "initialize_copy", ossl_mac_copy, 1); + rb_define_method(cMAC, "update", ossl_mac_update, 1); + rb_define_alias(cMAC, "<<", "update"); + rb_define_method(cMAC, "mac", ossl_mac_mac, 0); + + cCMAC = rb_define_class_under(cMAC, "CMAC", cMAC); + rb_define_method(cCMAC, "initialize", ossl_cmac_initialize, 2); + + eMACError = rb_define_class_under(mOSSL, "MACError", eOSSLError); +} +#else +void +Init_ossl_mac(void) +{ +} +#endif diff --git a/ext/openssl/ossl_mac.h b/ext/openssl/ossl_mac.h new file mode 100644 index 000000000..e038720d5 --- /dev/null +++ b/ext/openssl/ossl_mac.h @@ -0,0 +1,6 @@ +#if !defined(_OSSL_MAC_H_) +#define _OSSL_MAC_H_ + +void Init_ossl_mac(void); + +#endif /* _OSSL_MAC_H_ */ diff --git a/lib/openssl.rb b/lib/openssl.rb index 088992389..45fcb6f98 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/mac' require_relative 'openssl/x509' require_relative 'openssl/ssl' require_relative 'openssl/pkcs5' diff --git a/lib/openssl/mac.rb b/lib/openssl/mac.rb new file mode 100644 index 000000000..0e0a9ba4e --- /dev/null +++ b/lib/openssl/mac.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module OpenSSL + if defined?(MAC) + class MAC + def ==(other) + return false unless self.class === 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 CMAC < MAC + class << self + def mac(cipher, key, message) + cmac = new(cipher, key) + cmac << message + cmac.send(__callee__) + end + alias hexmac mac + alias base64mac mac + end + end + end + end +end diff --git a/test/openssl/test_cmac.rb b/test/openssl/test_cmac.rb new file mode 100644 index 000000000..dc88691c8 --- /dev/null +++ b/test/openssl/test_cmac.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true +require_relative "utils" + +if defined?(OpenSSL::MAC::CMAC) + +class OpenSSL::TestCMAC < OpenSSL::TestCase + def test_cmac + cmac = OpenSSL::MAC::CMAC.new("AES-128-CBC", ["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 + end + + def test_dup + cmac1 = OpenSSL::MAC::CMAC.new("AES-192-CBC", ["8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"].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 ["28a7023f452e8f82bd4bf28d8c37c35c"].pack("H*"), OpenSSL::MAC::CMAC.mac("AES-256-CBC", ["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal "28a7023f452e8f82bd4bf28d8c37c35c", OpenSSL::MAC::CMAC.hexmac("AES-256-CBC", ["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + assert_equal "KKcCP0Uuj4K9S/KNjDfDXA==", OpenSSL::MAC::CMAC.base64mac("AES-256-CBC", ["603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"].pack("H*"), ["6bc1bee22e409f96e93d7e117393172a"].pack("H*")) + end +end + +end