Skip to content

Add support for CMAC #891

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Add support for CMAC #891

wants to merge 4 commits into from

Conversation

kmfukuda
Copy link

Follow-up to #806.

Implements #802 by adding the OpenSSL::MAC and OpenSSL::MAC::CMAC classes.

The classes are defined if compiled against OpenSSL 3.

@kmfukuda kmfukuda mentioned this pull request May 21, 2025
@rhenium
Copy link
Member

rhenium commented May 21, 2025

Thanks for working on this series.

The base class OpenSSL::MAC doesn't seem to be useful on its own currently. The EVP_MAC API seems quite generic, so I wonder if we can expose the OSSL_PARAM construction part and the EVP_MAC_init() call as a method under OpenSSL::MAC that accepts arbitrary (parameter, value) pairs. That way, OpenSSL::MAC::CMAC (or other MACs supported by EVP_MAC) could be implemented entirely in lib/openssl/mac.rb without requiring a specialized extension method.

Regarding the method naming, is there particular reason for choosing #{,hex,base64}mac instead of #*digest? Since OpenSSL::HMAC already uses #*digest, I think copying it would have less friction, though I agree it might not have been the best choice originally.

The #*mac naming seems redundant, especially when used with the shorthand methods like OpenSSL::MAC::CMAC.mac(ciph, key, data). Another idea would be to use #final taken from the C API, which is what OpenSSL::Cipher does. That said I'm not sure what name I'd give to the variant that returns the hexadecimal string.

Also, we need docs.

@@ -0,0 +1,174 @@
#include "ossl.h"

#if OSSL_OPENSSL_PREREQ(3, 0, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe LibreSSL and AWS-LC will eventually implement the EVP_MAC API too. Please use an extconf.rb check instead of relying on the version number.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does any of the following approaches align with what you're suggesting?

  • OpenSSL::MAC should be defined if all necessary OpenSSL functions are available.
  • OpenSSL::MAC should be defined if a preselected OpenSSL function is available.
  • Each OpenSSL::MAC method should raise an exception unless all OpenSSL functions it needs are available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can assume the type EVP_MAC{,_CTX} and the related functions will be added in the same release, so checking any one of them should be sufficient.

def hexmac
mac.unpack1('H*')
end
alias inspect hexmac
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think people would expect #inspect to show the class name or the algorithm rather than the calculated MAC.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This aims to align OpenSSL::MAC subclasses with OpenSSL::HMAC to facilitate future migrations to OpenSSL::MAC::HMAC .
OpenSSL::HMAC has inspect as an alias for hexdigest , though it's unlikely that anyone would depend on that alias in production code.
If OpenSSL::HMAC didn't exist, I'd prefer OpenSSL::MAC::CMAC#inspect to behave exactly like Object#inspect .
Considering this, should we remove OpenSSL::MAC#inspect ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point it's in OpenSSL::HMAC. IMHO OpenSSL::MAC doesn't have to follow it.

The digest library (and thus OpenSSL::Digest)'s #inspect seems to return an Object#inspect-style string.

return false unless self.mac.bytesize == other.mac.bytesize

OpenSSL.fixed_length_secure_compare(self.mac, other.mac)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing MACs generated by different algorithms or parameters would be pointless, but this doesn't prevent it and it's probably not possible unless we store parameters or sensitive keys ourselves, which would be a bad idea.

Honestly, I'm not sure if this should be provided by this library.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, too, aims to align OpenSSL::MAC subclasses with OpenSSL::HMAC to facilitate future migrations to OpenSSL::MAC::HMAC .
OpenSSL::HMAC has an == method similar to this one, thereby comparing its objects as calculated values.
That said, objects of these classes are essentially calculators.
There seems little need to compare them as such, and as you indicate, no appropriate way to implement such a comparison.
If OpenSSL::HMAC didn't exist, I'd prefer to treat OpenSSL::MAC::CMAC objects only as calculators and avoid adding this == method.
Considering this, should we remove OpenSSL::MAC#== ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've forgotten it was in OpenSSL::HMAC. It seems to have the same issue.

IMO we should not add it to OpenSSL::MAC, though we can't remove the existing method from OpenSSL::HMAC.

@kmfukuda
Copy link
Author

kmfukuda commented Jul 1, 2025

Thanks for the second review.

That way, OpenSSL::MAC::CMAC (or other MACs supported by EVP_MAC ) could be implemented entirely in lib/openssl/mac.rb without requiring a specialized extension method.

I think we can do it the way you suggest.
However, another approach has occurred to me: OpenSSL::MAC#initialize would also accept key and parameters and call EVP_MAC_init() .
Here, parameters would be an array of objects of the future OpenSSL::Parameter class, which would represent OSSL_PARAM to a certain extent.
I don't currently see which approach is better.
What do you think about these approaches?

Regarding the method naming, is there particular reason for choosing #{,hex,base64}mac instead of #*digest ?

I chose the method names like "mac" because OpenSSL refers to MACs as "MACs," distinguishing them from "digests."
That said, I admit these method names are redundant when used with class names like "MAC."
I propose "value" as yet another option. This is because the term "MAC value" is frequently seen, and since "value" is a noun, it can be preceded by modifiers.

@rhenium
Copy link
Member

rhenium commented Jul 4, 2025

I leaning towards sticking with the *digest naming here. We'll likely want a variant that returns a hexadecimal string and one-shot methods for OpenSSL::MAC, similar to what the digest or OpenSSL::HMAC already provides. Using "final" (from the C API) or "value" would require inventing a new naming scheme just for OpenSSL::MAC which I prefer not to do.

Digging through the history, it appears OpenSSL::HMAC initially had method names #hmac and #hexhmac in 2002. They were later renamed to #digest and #hexdigest around when the one-shot class methods were added. This seems like a it was a deliberate change.

rhenium@9eb38c6

@rhenium
Copy link
Member

rhenium commented Jul 4, 2025

That way, OpenSSL::MAC::CMAC (or other MACs supported by EVP_MAC ) could be implemented entirely in lib/openssl/mac.rb without requiring a specialized extension method.

I think we can do it the way you suggest. However, another approach has occurred to me: OpenSSL::MAC#initialize would also accept key and parameters and call EVP_MAC_init() . Here, parameters would be an array of objects of the future OpenSSL::Parameter class, which would represent OSSL_PARAM to a certain extent. I don't currently see which approach is better. What do you think about these approaches?

FWIW, in #906, I've been working on exposing EVP_KDF that was also added in OpenSSL 3.0 and similarly takes an array of OSSL_PARAM for input. It's not set in stone and the PR is not finished yet, but I'm thinking of an interface to take them at one time as an enumerable of (key, value) tuples.

EVP_KEM mentioned in #894 (comment) (not being worked on yet) also takes it.

I'm hoping they can have a consistent behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants