class HTTPAuth::Digest::Utils
Utils
contains all sort of conveniance methods for the header container classes. Implementations shouldn’t have to call any methods on Utils
.
Public Class Methods
Calculate the digest value for the directives as explained in the RFC.
-
variant
: Either:request
or:response
, as seen from the server.
# File lib/httpauth/digest.rb, line 194 def calculate_digest(h, s, variant) fail(ArgumentError, "Variant should be either :request or :response, not #{variant}") unless [:request, :response].include?(variant) # Compatability with RFC 2069 if h[:qop].nil? digest_kd digest_a1(h, s), digest_concat( h[:nonce], send("#{variant}_digest_a2".intern, h) ) else digest_kd digest_a1(h, s), digest_concat( h[:nonce], Conversions.int_to_hex(h[:nc]), h[:cnonce], h[:qop], send("#{variant}_digest_a2".intern, h) ) end end
Create a nonce value of the time and a salt. The nonce is created in such a way that the issuer can check the age of the nonce.
-
salt
: A reasonably long passphrase known only to the issuer.
# File lib/httpauth/digest.rb, line 227 def create_nonce(salt) now = Time.now time = now.strftime('%Y-%m-%d %H:%M:%S').to_s + ':' + now.usec.to_s Base64.encode64( digest_concat( time, digest_h(digest_concat(time, salt)) ) ).gsub("\n", '')[0..-3] end
Create a 32 character long opaque string with a ‘random’ value
# File lib/httpauth/digest.rb, line 239 def create_opaque s = [] 16.times { s << rand(127).chr } digest_h s.join end
Decodes digest directives from a header. Returns a hash with directives.
-
directives
: The directives -
variant
: Specifies whether the directives are for an Authorize header (:credentials), for a WWW-Authenticate header (:challenge) or for a Authentication-Info header (:auth_info).
# File lib/httpauth/digest.rb, line 89 def decode_directives(directives, variant) fail(HTTPAuth::UnwellformedHeader, "Can't decode directives which are nil") if directives.nil? decode = {:domain => :space_quoted_string_to_list, :algorithm => false, :stale => :str_to_bool, :nc => :hex_to_int} if [:credentials, :auth].include? variant decode.merge! :qop => false elsif variant == :challenge decode.merge! :qop => :comma_quoted_string_to_list else fail(ArgumentError, "#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge") end start = 0 unless variant == :auth # The first six characters are 'Digest ' start = 6 scheme = directives[0..6].strip fail(HTTPAuth::UnwellformedHeader, "Scheme should be Digest, server responded with `#{directives}'") unless scheme == 'Digest' end # The rest are the directives # TODO: split is ugly, I want a real parser (: directives[start..-1].split(',').inject({}) do |h, part| parts = part.split('=') name = parts[0].strip.intern value = parts[1..-1].join('=').strip # --- HACK # IE and Safari qoute qop values # IE also quotes algorithm values if variant != :challenge && [:qop, :algorithm].include?(name) && value =~ /^\"[^\"]+\"$/ value = Conversions.unquote_string(value) end # --- END HACK if decode[name] h[name] = Conversions.send decode[name], value elsif decode[name].nil? h[name] = Conversions.unquote_string value else h[name] = value end h end end
Calculate the H(A1) as explain in the RFC. If h is set, it’s used instead of calculating H(username “:” realm “:” password).
# File lib/httpauth/digest.rb, line 159 def digest_a1(h, s) # TODO: check for known algorithm values (look out for the IE algorithm quote bug) if h[:algorithm] == 'MD5-sess' digest_h digest_concat( h[:digest] || htdigest(h[:username], h[:realm], h[:password]), h[:nonce], h[:cnonce] ) else h[:digest] || htdigest(h[:username], h[:realm], h[:password]) end end
Concat arguments the way it’s done frequently in the Digest
spec.
digest_concat('a', 'b') #=> "a:b" digest_concat('a', 'b', c') #=> "a:b:c"
# File lib/httpauth/digest.rb, line 138 def digest_concat(*args) args.join ':' end
Calculate the MD5 hexdigest for the string data
# File lib/httpauth/digest.rb, line 143 def digest_h(data) ::Digest::MD5.hexdigest data end
Calculate the KD value of a secret and data as explained in the RFC.
# File lib/httpauth/digest.rb, line 148 def digest_kd(secret, data) digest_h digest_concat(secret, data) end
Encodes a hash with digest directives to send in a header.
-
h
: The directives specified in a hash -
variant
: Specifies whether the directives are for an Authorize header (:credentials), for a WWW-Authenticate header (:challenge) or for a Authentication-Info header (:auth_info).
# File lib/httpauth/digest.rb, line 56 def encode_directives(h, variant) encode = {:domain => :list_to_space_quoted_string, :algorithm => false, :stale => :bool_to_str, :nc => :int_to_hex} if [:credentials, :auth].include? variant encode.merge! :qop => false elsif variant == :challenge encode.merge! :qop => :list_to_comma_quoted_string else fail(ArgumentError, "#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge") end (variant == :auth ? '' : 'Digest ') + h.collect do |directive, value| '' << directive.to_s << '=' << if encode[directive] begin Conversions.send encode[directive], value rescue NoMethodError, ArgumentError raise(ArgumentError, "Can't encode #{directive}(#{value.inspect}) with #{encode[directive]}") end elsif encode[directive].nil? begin Conversions.quote_string value rescue NoMethodError, ArgumentError raise(ArgumentError, "Can't encode #{directive}(#{value.inspect}) with quote_string") end else value end end.join(', ') end
Return a hash with the keys in keys
found in h
.
Example
filter_h_on({1=>1,2=>2}, [1]) #=> {1=>1} filter_h_on({1=>1,2=>2}, [1, 2]) #=> {1=>1,2=>2}
# File lib/httpauth/digest.rb, line 219 def filter_h_on(h, keys) h.inject({}) { |a, e| keys.include?(e[0]) ? a.merge(e[0] => e[1]) : a } end
Calculate the Digest
for the credentials
# File lib/httpauth/digest.rb, line 153 def htdigest(username, realm, password) digest_h digest_concat(username, realm, password) end
Calculate the H(A2) for the Authorize header as explained in the RFC.
# File lib/httpauth/digest.rb, line 173 def request_digest_a2(h) # TODO: check for known qop values (look out for the safari qop quote bug) if h[:qop] == 'auth-int' digest_h digest_concat(h[:method], h[:uri], digest_h(h[:request_body])) else digest_h digest_concat(h[:method], h[:uri]) end end
Calculate the H(A2) for the Authentication-Info header as explained in the RFC.
# File lib/httpauth/digest.rb, line 183 def response_digest_a2(h) if h[:qop] == 'auth-int' digest_h ':' + digest_concat(h[:uri], digest_h(h[:response_body])) else digest_h ':' + h[:uri] end end