Class HTTPAuth::Digest::Utils
In: lib/httpauth/digest.rb
Parent: Object

Utils contains all sort of conveniance methods for the header container classes. Implementations shouldn‘t have to call any methods on Utils.

Methods

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.

[Source]

     # File lib/httpauth/digest.rb, line 190
190:         def calculate_digest(h, s, variant)
191:           raise ArgumentError.new("Variant should be either :request or :response, not #{variant}") unless [:request, :response].include?(variant)
192:           # Compatability with RFC 2069
193:           if h[:qop].nil?
194:             digest_kd digest_a1(h, s), digest_concat(
195:               h[:nonce],
196:               send("#{variant}_digest_a2".intern, h)
197:             )
198:           else
199:             digest_kd digest_a1(h, s), digest_concat(
200:               h[:nonce],
201:               Conversions.int_to_hex(h[:nc]),
202:               h[:cnonce],
203:               h[:qop],
204:               send("#{variant}_digest_a2".intern, h)
205:             )
206:           end
207:         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.

[Source]

     # File lib/httpauth/digest.rb, line 223
223:         def create_nonce(salt)
224:           now = Time.now
225:           time = now.strftime("%Y-%m-%d %H:%M:%S").to_s + ':' + now.usec.to_s
226:           Base64.encode64(
227:           digest_concat(
228:               time,
229:               digest_h(digest_concat(time, salt))
230:             )
231:           ).gsub("\n", '')[0..-3]
232:         end

Create a 32 character long opaque string with a ‘random’ value

[Source]

     # File lib/httpauth/digest.rb, line 235
235:         def create_opaque
236:           s = []; 16.times { s << rand(127).chr }
237:           digest_h s.join
238:         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).

[Source]

     # File lib/httpauth/digest.rb, line 90
 90:         def decode_directives(directives, variant)
 91:           raise HTTPAuth::UnwellformedHeader.new("Can't decode directives which are nil") if directives.nil?
 92:           decode = {:domain => :split, :algorithm => false, :stale => :bool_to_str, :nc => :hex_to_int,
 93:                     :nextnonce => :hex_to_int}
 94:           if [:credentials, :auth].include? variant
 95:             decode.merge! :qop => false
 96:           elsif variant == :challenge
 97:             decode.merge! :qop => :quoted_string_to_list
 98:           else
 99:             raise ArgumentError.new("#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge")
100:           end
101:         
102:           start = 0 
103:           unless variant == :auth 
104:             # The first six characters are 'Digest '
105:             start = 6
106:             scheme = directives[0..6].strip
107:             raise HTTPAuth::UnwellformedHeader.new("Scheme should be Digest, server responded with `#{directives}'") unless scheme == 'Digest'
108:           end
109:           
110:           # The rest are the directives
111:           # TODO: split is ugly, I want a real parser (:
112:           directives[start..-1].split(',').inject({}) do |h,part|
113:             parts = part.split('=')
114:             name = parts[0].strip.intern
115:             value = parts[1..-1].join('=').strip
116:             
117:             # --- HACK
118:             # IE and Safari qoute qop values
119:             # IE also quotes algorithm values
120:             if variant != :challenge and [:qop, :algorithm].include?(name) and value =~ /^\"[^\"]+\"$/
121:               value = Conversions.unquote_string(value)
122:             end
123:             # --- END HACK
124:             
125:             if decode[name]
126:               h[name] = Conversions.send decode[name], value
127:             elsif decode[name].nil?
128:               h[name] = Conversions.unquote_string value
129:             else
130:               h[name] = value
131:             end
132:             h
133:           end
134:         end

Calculate the H(A1) as explain in the RFC. If h[:digest] is set, it‘s used instead of calculating H(username ":" realm ":" password).

[Source]

     # File lib/httpauth/digest.rb, line 155
155:         def digest_a1(h, s)
156:           # TODO: check for known algorithm values (look out for the IE algorithm quote bug)
157:           if h[:algorithm] == 'MD5-sess'
158:             digest_h digest_concat(
159:               h[:digest] || htdigest(h[:username], h[:realm], h[:password]),
160:               h[:nonce],
161:               h[:cnonce]
162:             )
163:           else
164:             h[:digest] || htdigest(h[:username], h[:realm], h[:password])
165:           end
166:         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"

[Source]

     # File lib/httpauth/digest.rb, line 140
140:         def digest_concat(*args); args.join ':'; end

Calculate the MD5 hexdigest for the string data

[Source]

     # File lib/httpauth/digest.rb, line 143
143:         def digest_h(data); ::Digest::MD5.hexdigest data; end

Calculate the KD value of a secret and data as explained in the RFC.

[Source]

     # File lib/httpauth/digest.rb, line 146
146:         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).

[Source]

    # File lib/httpauth/digest.rb, line 56
56:         def encode_directives(h, variant)
57:           encode = {:domain => :join, :algorithm => false, :stale => :str_to_bool, :nc => :int_to_hex,
58:                     :nextnonce => :int_to_hex}
59:           if [:credentials, :auth].include? variant
60:             encode.merge! :qop => false
61:           elsif variant == :challenge
62:             encode.merge! :qop => :list_to_quoted_string
63:           else
64:             raise ArgumentError.new("#{variant} is not a valid value for `variant' use :auth, :credentials or :challenge")
65:           end
66:           (variant == :auth ? '' : 'Digest ') + h.collect do |directive, value|
67:             '' << directive.to_s << '=' << if encode[directive]
68:                 begin
69:                   Conversions.send encode[directive], value
70:                 rescue NoMethodError, ArgumentError
71:                   raise ArgumentError.new("Can't encode #{directive}(#{value.inspect}) with #{encode[directive]}")
72:                 end
73:               elsif encode[directive].nil?
74:                 begin
75:                   Conversions.quote_string value
76:                 rescue NoMethodError, ArgumentError
77:                   raise ArgumentError.new("Can't encode #{directive}(#{value.inspect}) with quote_string")
78:                 end
79:               else
80:                 value
81:               end
82:           end.join(", ")
83:         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}

[Source]

     # File lib/httpauth/digest.rb, line 215
215:         def filter_h_on(h, keys)
216:           h.inject({}) { |r,l| keys.include?(l[0]) ? r.merge({l[0]=>l[1]}) : r }
217:         end

Calculate the Digest for the credentials

[Source]

     # File lib/httpauth/digest.rb, line 149
149:         def htdigest(username, realm, password)
150:           digest_h digest_concat(username, realm, password)
151:         end

Calculate the H(A2) for the Authorize header as explained in the RFC.

[Source]

     # File lib/httpauth/digest.rb, line 169
169:         def request_digest_a2(h)
170:           # TODO: check for known qop values (look out for the safari qop quote bug)
171:           if h[:qop] == 'auth-int'
172:             digest_h digest_concat(h[:method], h[:uri], digest_h(h[:request_body]))
173:           else
174:             digest_h digest_concat(h[:method], h[:uri])
175:           end
176:         end

Calculate the H(A2) for the Authentication-Info header as explained in the RFC.

[Source]

     # File lib/httpauth/digest.rb, line 179
179:         def response_digest_a2(h)
180:           if h[:qop] == 'auth-int'
181:             digest_h ':' + digest_concat(h[:uri], digest_h(h[:response_body]))
182:           else
183:             digest_h ':' + h[:uri]
184:           end
185:         end

[Validate]