Class: Linzer::MLDSA::OpenSSLKey
- Defined in:
- lib/linzer/ml_dsa/openssl_key.rb
Overview
Requires OpenSSL 3.5+ with ML-DSA signature algorithms enabled. Some distributions ship OpenSSL 3.5+ with these disabled by crypto policy (see https://github.com/ruby/openssl/issues/1075), so callers should be prepared for OpenSSL::PKey::PKeyError on unsupported builds even when the OpenSSL version alone looks sufficient.
ML-DSA (FIPS 204) signing/verification backed directly by OpenSSL 3.5+, with no additional gem dependency. Supports all three parameter sets (ML-DSA-44/65/87).
Like Ed25519, ML-DSA is a "pure"/digest-less signature scheme: the RFC 9421 signature base is signed directly, with no prehashing.
Instance Attribute Summary collapse
-
#algorithm ⇒ String
readonly
The FIPS 204 parameter set this key was constructed for, e.g.
Attributes inherited from Key
Class Method Summary collapse
-
.deserialize_raw_or_encoded_key(material, algorithm) ⇒ OpenSSL::PKey::PKey
private
Builds an OpenSSL key from ML-DSA material of unknown shape: raw FIPS 204 bytes for
algorithm(sniffed by exact byte length, the same approach GemKey uses for the gem backend) or an OpenSSL-encoded PEM/DER key, handled as a fallback. -
.unwrap_raw_private_key(key) ⇒ String
private
Extracts the raw (expanded, seed-free) FIPS 204 private key bytes from an OpenSSL key's own PKCS8 encoding -- the inverse of OpenSSLKey.wrap_raw_private_key.
-
.unwrap_raw_public_key(key) ⇒ String
private
Extracts the raw FIPS 204 public key bytes from an OpenSSL key's own SubjectPublicKeyInfo encoding -- the inverse of OpenSSLKey.wrap_raw_public_key.
Instance Method Summary collapse
-
#backend ⇒ Symbol
:openssl -- which backend produced this key.
-
#initialize(material, params = {}) ⇒ OpenSSLKey
constructor
A new instance of OpenSSLKey.
-
#sign(data) ⇒ String
Signs data using the ML-DSA private key.
-
#validate_signature_parameters(parameters) ⇒ true
Validates that the HTTP
algparameter matches this key's algorithm. -
#verify(signature, data) ⇒ Boolean
Verifies a signature using the ML-DSA public key.
Methods inherited from Key
Constructor Details
#initialize(material, params = {}) ⇒ OpenSSLKey
Returns a new instance of OpenSSLKey.
73 74 75 76 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 73 def initialize(material, params = {}) @algorithm = String(params.fetch(:algorithm)) super end |
Instance Attribute Details
#algorithm ⇒ String (readonly)
Returns The FIPS 204 parameter set this key was
constructed for, e.g. "ml-dsa-44".
66 67 68 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 66 def algorithm @algorithm end |
Class Method Details
.deserialize_raw_or_encoded_key(material, algorithm) ⇒ OpenSSL::PKey::PKey
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Builds an OpenSSL key from ML-DSA material of unknown shape: raw
FIPS 204 bytes for algorithm (sniffed by exact byte length, the
same approach GemKey uses for the gem backend) or
an OpenSSL-encoded PEM/DER key, handled as a fallback. Sniffing is
scoped to algorithm's own raw sizes, not all three parameter
sets' sizes at once, material sized for a different parameter
set than requested falls through to the OpenSSL::PKey.read
fallback and fails there (raw bytes aren't valid PEM/DER), rather
than silently being accepted and mislabeled.
143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 143 def deserialize_raw_or_encoded_key(material, algorithm) raise TypeError, "ML-DSA key material must be a String" unless material.is_a?(String) case material.bytesize when RAW_PUBLIC_KEY_BYTES.fetch(algorithm) wrap_raw_public_key(material, algorithm) when RAW_PRIVATE_KEY_BYTES.fetch(algorithm) wrap_raw_private_key(material, algorithm) else OpenSSL::PKey.read(material) end end |
.unwrap_raw_private_key(key) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Extracts the raw (expanded, seed-free) FIPS 204 private key bytes
from an OpenSSL key's own PKCS8 encoding -- the inverse of
wrap_raw_private_key. Used to hand a key generated via this
backend to the ml_dsa gem backend instead.
OpenSSL's own generated keys always use the "both" alternative of the ML-DSA private key CHOICE (a seed alongside the expanded key, confirmed empirically -- see the strategy notes), so this only handles that shape; anything else raises rather than silently returning the wrong bytes.
185 186 187 188 189 190 191 192 193 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 185 def unwrap_raw_private_key(key) one_asymmetric_key = OpenSSL::ASN1.decode(key.private_to_der) private_key_choice = OpenSSL::ASN1.decode(one_asymmetric_key.value[2].value) unless private_key_choice.is_a?(OpenSSL::ASN1::Sequence) raise Error, "Unsupported ML-DSA private key encoding (expected seed+expandedKey)" end private_key_choice.value[1].value end |
.unwrap_raw_public_key(key) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Extracts the raw FIPS 204 public key bytes from an OpenSSL key's
own SubjectPublicKeyInfo encoding -- the inverse of
wrap_raw_public_key. Used to hand a key generated (or loaded)
via this backend to the ml_dsa gem backend instead.
164 165 166 167 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 164 def unwrap_raw_public_key(key) spki = OpenSSL::ASN1.decode(key.public_to_der) spki.value[1].value end |
Instance Method Details
#backend ⇒ Symbol
Returns :openssl -- which backend produced this key.
122 123 124 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 122 def backend :openssl end |
#sign(data) ⇒ String
Signs data using the ML-DSA private key.
97 98 99 100 101 102 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 97 def sign(data) validate_signing_key material.sign(nil, data) rescue OpenSSL::PKey::PKeyError => e raise SigningError, e., cause: e end |
#validate_signature_parameters(parameters) ⇒ true
Validates that the HTTP alg parameter matches this key's algorithm.
83 84 85 86 87 88 89 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 83 def validate_signature_parameters(parameters) supplied_algorithm = parameters["alg"] || parameters[:alg] return true if supplied_algorithm.nil? || supplied_algorithm == algorithm raise VerifyError, "Signature algorithm #{supplied_algorithm} does not match key algorithm #{algorithm}" end |
#verify(signature, data) ⇒ Boolean
Verifies a signature using the ML-DSA public key.
112 113 114 115 116 117 118 119 |
# File 'lib/linzer/ml_dsa/openssl_key.rb', line 112 def verify(signature, data) validate_verify_key return false unless signature.is_a?(String) material.verify(nil, signature, data) rescue OpenSSL::PKey::PKeyError, ArgumentError, TypeError false end |