Module: Linzer::MLDSA

Defined in:
lib/linzer/ml_dsa.rb,
lib/linzer/ml_dsa/gem_key.rb,
lib/linzer/ml_dsa/openssl_key.rb

Overview

ML-DSA support for HTTP Message Signatures as specified by https://c2sp.org/httpsig-pq

Two independent backends live under this namespace, both supporting all three FIPS 204 parameter sets (ML-DSA-44/65/87):

  • OpenSSLKey -- backed directly by OpenSSL 3.5+, no extra gem dependency.
  • GemKey -- backed by the ml_dsa gem.

require "linzer" alone already gets you OpenSSLKey for free when the running OpenSSL build supports it, that's the safe default, and this file plays no part in it (lib/linzer.rb requires ml_dsa/openssl_key directly). require "linzer/ml_dsa" is the explicit opt-in for everything else: it also loads GemKey, which in turn requires the ml_dsa gem to be installed. Reach for it when your OpenSSL build is too old for ML-DSA, or you specifically want the gem-backed implementation.

Linzer.generate_ml_dsa_*_key/Linzer.new_ml_dsa_*_key dispatch between them via a backend: keyword (:auto, :openssl, or :ml_dsa), preferring OpenSSL when this build actually supports it.

Defined Under Namespace

Classes: GemKey, OpenSSLKey

Constant Summary collapse

ALGORITHMS =
{
  "ml-dsa-44" => MlDsa::ML_DSA_44,
  "ml-dsa-65" => MlDsa::ML_DSA_65,
  "ml-dsa-87" => MlDsa::ML_DSA_87
}.freeze

Class Method Summary collapse

Class Method Details

.openssl_supported?(algorithm) ⇒ Boolean

Checks whether this OpenSSL build can actually perform ML-DSA signing/verification for the given algorithm.

Returns true only when the algorithm is both something this OpenSSL-backed implementation has actually implemented (see IMPLEMENTED_ALGORITHMS) and something the underlying OpenSSL library itself supports. This is needed mostly because a version-number check alone isn't reliable and some distributions ship OpenSSL 3.5+ with ML-DSA disabled by crypto policy (see https://github.com/ruby/openssl/issues/1075), so this actually attempts a throwaway key generation rather than inspecting OpenSSL::OPENSSL_VERSION.

Memoized per algorithm after the first check, so repeated calls are free. Unknown or not-yet-implemented algorithm identifiers return false rather than raising.

Kept at the Linzer::MLDSA module level, not under OpenSSLKey, since it's a backend-selection capability probe used to decide whether to construct an OpenSSLKey at all, not a detail of an already-chosen OpenSSL backend.

Parameters:

  • algorithm (String)

    Linzer's lowercase algorithm identifier, e.g. "ml-dsa-44"

Returns:

  • (Boolean)


319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 319

def openssl_supported?(algorithm)
  cache = (@openssl_supported ||= {})
  return cache[algorithm] if cache.key?(algorithm)

  cache[algorithm] =
    if IMPLEMENTED_ALGORITHMS.include?(algorithm)
      begin
        OpenSSL::PKey.generate_key(algorithm.upcase)
        true
      rescue OpenSSL::PKey::PKeyError
        false
      end
    else
      false
    end
end