Class: Altcha::Submission

Inherits:
Object
  • Object
show all
Defined in:
lib/altcha-rails.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(payload = {}) ⇒ Submission

Returns a new instance of Submission.



102
103
104
105
106
107
108
# File 'lib/altcha-rails.rb', line 102

def initialize(payload = {})
  @algorithm = payload["algorithm"].to_s
  @challenge = payload["challenge"].to_s
  @signature = payload["signature"].to_s
  @salt      = payload["salt"].to_s
  @number    = payload["number"]
end

Instance Attribute Details

#algorithmObject (readonly)

Returns the value of attribute algorithm.



87
88
89
# File 'lib/altcha-rails.rb', line 87

def algorithm
  @algorithm
end

#challengeObject (readonly)

Returns the value of attribute challenge.



87
88
89
# File 'lib/altcha-rails.rb', line 87

def challenge
  @challenge
end

#numberObject (readonly)

Returns the value of attribute number.



87
88
89
# File 'lib/altcha-rails.rb', line 87

def number
  @number
end

#saltObject (readonly)

Returns the value of attribute salt.



87
88
89
# File 'lib/altcha-rails.rb', line 87

def salt
  @salt
end

#signatureObject (readonly)

Returns the value of attribute signature.



87
88
89
# File 'lib/altcha-rails.rb', line 87

def signature
  @signature
end

Class Method Details

.verify(base64_string) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/altcha-rails.rb', line 89

def self.verify(base64_string)
  raw = begin
    Base64.decode64(base64_string.to_s)
  rescue ArgumentError
    return nil
  end
  payload = JSON.parse(raw) rescue nil
  return nil unless payload.is_a?(Hash)

  submission = new(payload)
  submission.valid? ? submission : nil
end

Instance Method Details

#valid?Boolean

Returns:

  • (Boolean)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/altcha-rails.rb', line 110

def valid?
  return false unless @algorithm == Altcha.algorithm
  return false unless @number.is_a?(Integer)
  return false if Altcha.hmac_key.nil? || Altcha.hmac_key.empty?

  expires = extract_expires(@salt)
  return false if expires.nil?
  return false unless Time.at(expires) > Time.now

  # Normalize to canonical trailing-'&' form before recomputing the hash;
  # a spliced salt no longer round-trips to the same digest. Mitigates
  # CVE-2025-68113.
  canonical_salt = @salt.end_with?("&") ? @salt : "#{@salt}&"
  check = Digest::SHA256.hexdigest(canonical_salt + @number.to_s)

  return false unless @challenge == check

  expected_sig = OpenSSL::HMAC.hexdigest(
    OpenSSL::Digest.new(@algorithm), Altcha.hmac_key, check
  )
  secure_compare(@signature, expected_sig)
end