Skip to content

Identify ML-DSA by OID and gate ML-DSA tests on the capability they need - #9853

Open
justsmth wants to merge 2 commits into
ruby:masterfrom
justsmth:mldsa-oid-and-load-gating
Open

Identify ML-DSA by OID and gate ML-DSA tests on the capability they need#9853
justsmth wants to merge 2 commits into
ruby:masterfrom
justsmth:mldsa-oid-and-load-gating

Conversation

@justsmth

@justsmth justsmth commented Sep 3, 2026

Copy link
Copy Markdown

What was the end-user or developer problem that led to this PR?

RubyGems currently assumes an SSL library either supports all ML-DSA operations or none of them:

  • Gem::Security.ml_dsa_key? identifies ML-DSA using OpenSSL 3.5 algorithm names such as ML-DSA-65. Those names are library-specific; AWS-LC registers the same standard OID as MLDSA65, so RubyGems rejects a valid ML-DSA key as unsupported and cannot verify ML-DSA signed gems.
  • Gem::PQCUtilities.support_ml_dsa_key? probes key generation but gates tests that only load checked-in keys and certificates. AWS-LC loads, signs, and verifies with ML-DSA, but does not generate an ML-DSA key by algorithm name. This causes four incorrect test failures and skips ten positive tests that AWS-LC can run.

What is your fix for the problem, implemented in this PR?

ml_dsa_key? now identifies ML-DSA using the three standard SubjectPublicKeyInfo OIDs rather than library-specific display names.

The tests gain a separate support_ml_dsa_key_load? probe. Four negative tests that assert loading failure, plus ten positive fixture-based tests, now use the load capability instead of the generation capability. Tests that call Gem::Security.create_key keep the existing generation gate.

These changes are combined because they test each other: re-gating the positive tests without the OID fix exposes four failures in digest_required?, while the OID fix alone would remain untested on libraries that can load but not generate ML-DSA keys.

This continues 9a43535, which separated key generation from certificate signing but not key generation from key loading.

Testing

Using Ruby master built against AWS-LC 5.7.0:

Tests Assertions Failures Omissions
master 182 883 4 23
This PR 182 902 0 17

The net omission change reflects ten newly exercised positive tests and four negative tests now correctly omitted. Gem::Security::Policy#check_data verifies an ML-DSA signature and rejects a tampered payload with this change.

Using OpenSSL 3.0.13, which has no ML-DSA support, all five affected test files produce identical results before and after. The full RubyGems test suite passes, and bin/rubocop reports no offenses on the seven changed files.

I did not have an OpenSSL 3.5 build available locally. On 3.5 both capability probes remain true, and the OIDs matched here are the same OIDs exposed by its existing ML-DSA keys.

Make sure the following tasks are checked

The ML-DSA support checks assume an SSL library either supports ML-DSA
completely or not at all, and identify ML-DSA keys by the name OpenSSL 3.5
registers. Both break on libraries that implement ML-DSA differently: AWS-LC
loads ML-DSA keys and certificates and signs and verifies with them, but has
no keygen by algorithm name, and names OID 2.16.840.1.101.3.4.3.18 "MLDSA65".

ml_dsa_key? matches ML_DSA_NAMES against ObjectId#ln, so it fails to
recognise a valid ML-DSA key when the library spells the name differently,
and digest_required? then raises "unsupported key algorithm" for a key
RubyGems does support, making ML-DSA signed gems unusable there. Match the
SubjectPublicKeyInfo OID instead, which is identical on every library.
ML_DSA_NAMES becomes unused and is removed; the individual name constants
stay, since create_key still passes them to OpenSSL::PKey.generate_key.

support_ml_dsa_key? probes key generation but gates tests that only need
loading. Four tests assert the "no ML-DSA support" path for a key or
certificate read from disk, and run on a false premise where loading works
but generation does not: two raise nothing and two raise a different error.
Ten more use only the checked-in ML-DSA fixtures, yet are skipped for want
of a capability they never exercise. Add support_ml_dsa_key_load? and gate
those fourteen on it. test_gem_security.rb is unchanged, since its ML-DSA
tests build keys with Gem::Security.create_key and do need the generation
gate.

Continues 9a43535, which separated generation from nil-digest certificate
signing but not generation from loading.
Comment thread test/rubygems/pqc_utilities.rb
Comment thread lib/rubygems/security.rb
@justsmth
justsmth requested a review from junaruga September 3, 2026 20:23
@junaruga

junaruga commented Sep 4, 2026

Copy link
Copy Markdown
Member

@justsmth It seems the ruby/rubygems repository doesn't have the GitHub Actions CI case for Ruby with ruby/openssl built with AWS-LC right now. Perhaps, adding the AWS-LC latest stable version case to GitHub Actions to this repository may be helpful if we consider AWS-LC case. I haven't checked if this PR's modified tests pass for RubyGems with Ruby OpenSSL built with AWS-LC.

As a reference, ruby/openssl repository has the AWS-LC latest stable version case on the CI.

https://github.com/ruby/openssl/blob/2f04ba404e964be65c24a9da93bb7cec8570b320/.github/workflows/test.yml#L157-L163

There is a trade-off about adding the AWS-LC case to the CI.

Pros:

  • It's easy to check AWS-LC case, and prevent us from breaking AWS-LC case.

Cons:

  • Cost to maintain the case.
  • Cost to run the CI case in term of infra resource.

I am not a maintainer of the ruby/rubygems. So, consider my opinion as assumption which may not happen. If you work on this before maintainers agree the direction, you may waste your time. The steps can be as follows.

  1. Cache compiled AWS-LC. We don't need to compile again if the existing AWS-LC version is cached.
  2. Compile AWS-LC latest stable version by downloading the source from the AWS-LC repository https://github.com/aws/aws-lc.
  3. Reinstall Ruby OpenSSL with the compiled AWS-LC by gem install openssl -- --with-openssl-dir=/path/to/aws-lc.

The candidate GitHub Actions yml file to add this case is .github/workflows/rubygems.yml including Ruby variants (JRuby and Truffleruby) or new yml file for SSL library variants.

Until ruby/rubygems maintainers comment on this PR, I don't have anything to comment on this PR. I pend my approval for this PR for now, while I saw you requested a review from me using the PR's function.

Let's wait for the maintainer's reviews.

@hsbt

hsbt commented Sep 7, 2026

Copy link
Copy Markdown
Member

About the AWS-LC job, I am ok to add it. But I do not want to build AWS-LC on every run. If we can cache the build like your step 1, the job is worth it.

@junaruga For the change itself, could you review this?

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.

3 participants