Module: PQCrypto::JWT::JWA::MLDSAStreaming

Included in:
MLDSA65
Defined in:
lib/pq_crypto/jwt/jwa/ml_dsa_streaming.rb

Constant Summary collapse

DEFAULT_CHUNK_SIZE =
1 << 20
EMPTY_CONTEXT =
"".b.freeze

Instance Method Summary collapse

Instance Method Details

#sign_io(signing_key:, payload_io: nil, io: nil, header_fields: {}, chunk_size: DEFAULT_CHUNK_SIZE) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/pq_crypto/jwt/jwa/ml_dsa_streaming.rb', line 18

def sign_io(signing_key:, payload_io: nil, io: nil, header_fields: {}, chunk_size: DEFAULT_CHUNK_SIZE)
  raise PQCrypto::JWT::UnsupportedAlgorithm, "#{alg} does not support streaming JWS" unless streaming_supported?

  ensure_secret_key!(signing_key)
  source = payload_io || io
  raise ArgumentError, "payload_io must respond to #read" unless source.respond_to?(:read)

  header = stringify_keys(header_fields || {}).merge("alg" => alg)
  encoded_header = base64url(JSON.generate(header))
  signing_input = DetachedSigningInputIO.new(encoded_header, source, chunk_size: chunk_size)
  signature = signing_key.sign_io(signing_input, chunk_size: chunk_size, context: EMPTY_CONTEXT)
  "#{encoded_header}..#{base64url(signature)}"
rescue PQCrypto::JWT::Error, ArgumentError
  raise
rescue StandardError => e
  raise ::JWT::EncodeError, e.message
end

#streaming_supported?Boolean

Returns:

  • (Boolean)


13
14
15
16
# File 'lib/pq_crypto/jwt/jwa/ml_dsa_streaming.rb', line 13

def streaming_supported?
  pq_crypto_algorithm == :ml_dsa_65 &&
    PQCrypto::Signature.supported.include?(:ml_dsa_65)
end

#verify_io(verification_key:, token:, payload_io:, chunk_size: DEFAULT_CHUNK_SIZE) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/pq_crypto/jwt/jwa/ml_dsa_streaming.rb', line 36

def verify_io(verification_key:, token:, payload_io:, chunk_size: DEFAULT_CHUNK_SIZE)
  raise PQCrypto::JWT::UnsupportedAlgorithm, "#{alg} does not support streaming JWS" unless streaming_supported?
  ensure_public_key!(verification_key)
  raise ArgumentError, "token must be a String" unless token.is_a?(String)
  raise ArgumentError, "payload_io must respond to #read" unless payload_io.respond_to?(:read)

  encoded_header, encoded_payload, encoded_signature = token.split(".", -1)
  return false unless encoded_header && encoded_payload == "" && encoded_signature

  header = JSON.parse(Base64.urlsafe_decode64(encoded_header))
  return false unless header["alg"] == alg

  signature = Base64.urlsafe_decode64(encoded_signature)
  signing_input = DetachedSigningInputIO.new(encoded_header, payload_io, chunk_size: chunk_size)
  verified = verification_key.verify_io(signing_input, signature, chunk_size: chunk_size, context: EMPTY_CONTEXT)
  return false unless verified

  [payload_position(payload_io), header]
rescue JSON::ParserError, ArgumentError, PQCrypto::InvalidKeyError
  false
end

#verify_io!(verification_key:, token:, payload_io:, chunk_size: DEFAULT_CHUNK_SIZE) ⇒ Object

Raises:

  • (::JWT::VerificationError)


58
59
60
61
62
63
# File 'lib/pq_crypto/jwt/jwa/ml_dsa_streaming.rb', line 58

def verify_io!(verification_key:, token:, payload_io:, chunk_size: DEFAULT_CHUNK_SIZE)
  result = verify_io(verification_key: verification_key, token: token, payload_io: payload_io, chunk_size: chunk_size)
  raise ::JWT::VerificationError, "Streaming JWS verification failed" unless result

  true
end