C0 code coverage information
Generated on Fri Jul 11 15:55:32 -0700 2008 with rcov 0.7.0
Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
1 require "openid/kvform"
2 require "openid/util"
3 require "openid/cryptutil"
4 require "openid/message"
5
6 module OpenID
7
8 def self.get_secret_size(assoc_type)
9 if assoc_type == 'HMAC-SHA1'
10 return 20
11 elsif assoc_type == 'HMAC-SHA256'
12 return 32
13 else
14 raise ArgumentError("Unsupported association type: #{assoc_type}")
15 end
16 end
17
18 # An Association holds the shared secret between a relying party and
19 # an OpenID provider.
20 class Association
21 attr_reader :handle, :secret, :issued, :lifetime, :assoc_type
22
23 FIELD_ORDER =
24 [:version, :handle, :secret, :issued, :lifetime, :assoc_type,]
25
26 # Load a serialized Association
27 def self.deserialize(serialized)
28 parsed = Util.kv_to_seq(serialized)
29 parsed_fields = parsed.map{|k, v| k.to_sym}
30 if parsed_fields != FIELD_ORDER
31 raise ProtocolError, 'Unexpected fields in serialized association'\
32 " (Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})"
33 end
34 version, handle, secret64, issued_s, lifetime_s, assoc_type =
35 parsed.map {|field, value| value}
36 if version != '2'
37 raise ProtocolError, "Attempted to deserialize unsupported version "\
38 "(#{parsed[0][1].inspect})"
39 end
40
41 self.new(handle,
42 Util.from_base64(secret64),
43 Time.at(issued_s.to_i),
44 lifetime_s.to_i,
45 assoc_type)
46 end
47
48 # Create an Association with an issued time of now
49 def self.from_expires_in(expires_in, handle, secret, assoc_type)
50 issued = Time.now
51 self.new(handle, secret, issued, expires_in, assoc_type)
52 end
53
54 def initialize(handle, secret, issued, lifetime, assoc_type)
55 @handle = handle
56 @secret = secret
57 @issued = issued
58 @lifetime = lifetime
59 @assoc_type = assoc_type
60 end
61
62 # Serialize the association to a form that's consistent across
63 # JanRain OpenID libraries.
64 def serialize
65 data = {
66 :version => '2',
67 :handle => handle,
68 :secret => Util.to_base64(secret),
69 :issued => issued.to_i.to_s,
70 :lifetime => lifetime.to_i.to_s,
71 :assoc_type => assoc_type,
72 }
73
74 Util.assert(data.length == FIELD_ORDER.length)
75
76 pairs = FIELD_ORDER.map{|field| [field.to_s, data[field]]}
77 return Util.seq_to_kv(pairs, strict=true)
78 end
79
80 # The number of seconds until this association expires
81 def expires_in(now=nil)
82 if now.nil?
83 now = Time.now.to_i
84 else
85 now = now.to_i
86 end
87 time_diff = (issued.to_i + lifetime) - now
88 if time_diff < 0
89 return 0
90 else
91 return time_diff
92 end
93 end
94
95 # Generate a signature for a sequence of [key, value] pairs
96 def sign(pairs)
97 kv = Util.seq_to_kv(pairs)
98 case assoc_type
99 when 'HMAC-SHA1'
100 CryptUtil.hmac_sha1(@secret, kv)
101 when 'HMAC-SHA256'
102 CryptUtil.hmac_sha256(@secret, kv)
103 else
104 raise ProtocolError, "Association has unknown type: "\
105 "#{assoc_type.inspect}"
106 end
107 end
108
109 # Generate the list of pairs that form the signed elements of the
110 # given message
111 def make_pairs(message)
112 signed = message.get_arg(OPENID_NS, 'signed')
113 if signed.nil?
114 raise ProtocolError, 'Missing signed list'
115 end
116 signed_fields = signed.split(',', -1)
117 data = message.to_post_args
118 signed_fields.map {|field| [field, data.fetch('openid.'+field,'')] }
119 end
120
121 # Return whether the message's signature passes
122 def check_message_signature(message)
123 message_sig = message.get_arg(OPENID_NS, 'sig')
124 if message_sig.nil?
125 raise ProtocolError, "#{message} has no sig."
126 end
127 calculated_sig = get_message_signature(message)
128 return calculated_sig == message_sig
129 end
130
131 # Get the signature for this message
132 def get_message_signature(message)
133 Util.to_base64(sign(make_pairs(message)))
134 end
135
136 def ==(other)
137 (other.class == self.class and
138 other.handle == self.handle and
139 other.secret == self.secret and
140
141 # The internals of the time objects seemed to differ
142 # in an opaque way when serializing/unserializing.
143 # I don't think this will be a problem.
144 other.issued.to_i == self.issued.to_i and
145
146 other.lifetime == self.lifetime and
147 other.assoc_type == self.assoc_type)
148 end
149
150 # Add a signature (and a signed list) to a message.
151 def sign_message(message)
152 if (message.has_key?(OPENID_NS, 'sig') or
153 message.has_key?(OPENID_NS, 'signed'))
154 raise ArgumentError, 'Message already has signed list or signature'
155 end
156
157 extant_handle = message.get_arg(OPENID_NS, 'assoc_handle')
158 if extant_handle and extant_handle != self.handle
159 raise ArgumentError, "Message has a different association handle"
160 end
161
162 signed_message = message.copy()
163 signed_message.set_arg(OPENID_NS, 'assoc_handle', self.handle)
164 message_keys = signed_message.to_post_args.keys()
165
166 signed_list = []
167 message_keys.each { |k|
168 if k.starts_with?('openid.')
169 signed_list << k[7..-1]
170 end
171 }
172
173 signed_list << 'signed'
174 signed_list.sort!
175
176 signed_message.set_arg(OPENID_NS, 'signed', signed_list.join(','))
177 sig = get_message_signature(signed_message)
178 signed_message.set_arg(OPENID_NS, 'sig', sig)
179 return signed_message
180 end
181 end
182
183 class AssociationNegotiator
184 attr_reader :allowed_types
185
186 def self.get_session_types(assoc_type)
187 case assoc_type
188 when 'HMAC-SHA1'
189 ['DH-SHA1', 'no-encryption']
190 when 'HMAC-SHA256'
191 ['DH-SHA256', 'no-encryption']
192 else
193 raise ProtocolError, "Unknown association type #{assoc_type.inspect}"
194 end
195 end
196
197 def self.check_session_type(assoc_type, session_type)
198 if !get_session_types(assoc_type).include?(session_type)
199 raise ProtocolError, "Session type #{session_type.inspect} not "\
200 "valid for association type #{assoc_type.inspect}"
201 end
202 end
203
204 def initialize(allowed_types)
205 self.allowed_types=(allowed_types)
206 end
207
208 def copy
209 Marshal.load(Marshal.dump(self))
210 end
211
212 def allowed_types=(allowed_types)
213 allowed_types.each do |assoc_type, session_type|
214 self.class.check_session_type(assoc_type, session_type)
215 end
216 @allowed_types = allowed_types
217 end
218
219 def add_allowed_type(assoc_type, session_type=nil)
220 if session_type.nil?
221 session_types = self.class.get_session_types(assoc_type)
222 else
223 self.class.check_session_type(assoc_type, session_type)
224 session_types = [session_type]
225 end
226 for session_type in session_types do
227 @allowed_types << [assoc_type, session_type]
228 end
229 end
230
231 def allowed?(assoc_type, session_type)
232 @allowed_types.include?([assoc_type, session_type])
233 end
234
235 def get_allowed_type
236 @allowed_types.empty? ? nil : @allowed_types[0]
237 end
238 end
239
240 DefaultNegotiator =
241 AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
242 ['HMAC-SHA1', 'no-encryption'],
243 ['HMAC-SHA256', 'DH-SHA256'],
244 ['HMAC-SHA256', 'no-encryption']])
245
246 EncryptedNegotiator =
247 AssociationNegotiator.new([['HMAC-SHA1', 'DH-SHA1'],
248 ['HMAC-SHA256', 'DH-SHA256']])
249 end
Generated using the rcov code coverage analysis tool for Ruby version 0.7.0.