Class: HTTPAuth::Digest::Utils

Inherits:
Object
  • Object
show all
Defined in:
lib/httpauth/digest.rb

Overview

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

Class Method Summary collapse

Class Method Details

.calculate_digest(h, s, variant) ⇒ Object

Calculate the digest value for the directives as explained in the RFC.

  • variant: Either :request or :response, as seen from the server.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# 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_nonce(salt) ⇒ Object

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.



227
228
229
230
231
232
233
234
235
236
# 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_opaqueObject

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



239
240
241
242
243
# File 'lib/httpauth/digest.rb', line 239

def create_opaque
  s = []
  16.times { s << rand(127).chr }
  digest_h s.join
end

.decode_directives(directives, variant) ⇒ Object

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).



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# 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

.digest_a1(h, s) ⇒ Object

Calculate the H(A1) as explain in the RFC. If h is set, it’s used instead of calculating H(username “:” realm “:” password).



159
160
161
162
163
164
165
166
167
168
169
170
# 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

.digest_concat(*args) ⇒ Object

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"


138
139
140
# File 'lib/httpauth/digest.rb', line 138

def digest_concat(*args)
  args.join ':'
end

.digest_h(data) ⇒ Object

Calculate the MD5 hexdigest for the string data



143
144
145
# File 'lib/httpauth/digest.rb', line 143

def digest_h(data)
  ::Digest::MD5.hexdigest data
end

.digest_kd(secret, data) ⇒ Object

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



148
149
150
# File 'lib/httpauth/digest.rb', line 148

def digest_kd(secret, data)
  digest_h digest_concat(secret, data)
end

.encode_directives(h, variant) ⇒ Object

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).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 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

.filter_h_on(h, keys) ⇒ Object

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}


219
220
221
# 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

.htdigest(username, realm, password) ⇒ Object

Calculate the Digest for the credentials



153
154
155
# File 'lib/httpauth/digest.rb', line 153

def htdigest(username, realm, password)
  digest_h digest_concat(username, realm, password)
end

.request_digest_a2(h) ⇒ Object

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



173
174
175
176
177
178
179
180
# 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

.response_digest_a2(h) ⇒ Object

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



183
184
185
186
187
188
189
# 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