ruby-openid2 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +136 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/CONTRIBUTING.md +54 -0
  6. data/LICENSE.txt +210 -0
  7. data/README.md +81 -0
  8. data/SECURITY.md +15 -0
  9. data/lib/hmac/hmac.rb +110 -0
  10. data/lib/hmac/sha1.rb +11 -0
  11. data/lib/hmac/sha2.rb +25 -0
  12. data/lib/openid/association.rb +246 -0
  13. data/lib/openid/consumer/associationmanager.rb +354 -0
  14. data/lib/openid/consumer/checkid_request.rb +179 -0
  15. data/lib/openid/consumer/discovery.rb +516 -0
  16. data/lib/openid/consumer/discovery_manager.rb +144 -0
  17. data/lib/openid/consumer/html_parse.rb +142 -0
  18. data/lib/openid/consumer/idres.rb +513 -0
  19. data/lib/openid/consumer/responses.rb +147 -0
  20. data/lib/openid/consumer/session.rb +36 -0
  21. data/lib/openid/consumer.rb +406 -0
  22. data/lib/openid/cryptutil.rb +112 -0
  23. data/lib/openid/dh.rb +84 -0
  24. data/lib/openid/extension.rb +38 -0
  25. data/lib/openid/extensions/ax.rb +552 -0
  26. data/lib/openid/extensions/oauth.rb +88 -0
  27. data/lib/openid/extensions/pape.rb +170 -0
  28. data/lib/openid/extensions/sreg.rb +268 -0
  29. data/lib/openid/extensions/ui.rb +49 -0
  30. data/lib/openid/fetchers.rb +277 -0
  31. data/lib/openid/kvform.rb +113 -0
  32. data/lib/openid/kvpost.rb +62 -0
  33. data/lib/openid/message.rb +555 -0
  34. data/lib/openid/protocolerror.rb +7 -0
  35. data/lib/openid/server.rb +1571 -0
  36. data/lib/openid/store/filesystem.rb +260 -0
  37. data/lib/openid/store/interface.rb +73 -0
  38. data/lib/openid/store/memcache.rb +109 -0
  39. data/lib/openid/store/memory.rb +79 -0
  40. data/lib/openid/store/nonce.rb +72 -0
  41. data/lib/openid/trustroot.rb +597 -0
  42. data/lib/openid/urinorm.rb +72 -0
  43. data/lib/openid/util.rb +119 -0
  44. data/lib/openid/version.rb +5 -0
  45. data/lib/openid/yadis/accept.rb +141 -0
  46. data/lib/openid/yadis/constants.rb +16 -0
  47. data/lib/openid/yadis/discovery.rb +151 -0
  48. data/lib/openid/yadis/filters.rb +192 -0
  49. data/lib/openid/yadis/htmltokenizer.rb +290 -0
  50. data/lib/openid/yadis/parsehtml.rb +50 -0
  51. data/lib/openid/yadis/services.rb +44 -0
  52. data/lib/openid/yadis/xrds.rb +160 -0
  53. data/lib/openid/yadis/xri.rb +86 -0
  54. data/lib/openid/yadis/xrires.rb +87 -0
  55. data/lib/openid.rb +27 -0
  56. data/lib/ruby-openid.rb +1 -0
  57. data.tar.gz.sig +0 -0
  58. metadata +331 -0
  59. metadata.gz.sig +0 -0
data/lib/hmac/hmac.rb ADDED
@@ -0,0 +1,110 @@
1
+ # Copyright (C) 2001 Daiki Ueno <ueno@unixuser.org>
2
+ # This library is distributed under the terms of the Ruby license.
3
+
4
+ # This module provides common interface to HMAC engines.
5
+ # HMAC standard is documented in RFC 2104:
6
+ #
7
+ # H. Krawczyk et al., "HMAC: Keyed-Hashing for Message Authentication",
8
+ # RFC 2104, February 1997
9
+ #
10
+ # These APIs are inspired by JCE 1.2's javax.crypto.Mac interface.
11
+ #
12
+ # <URL:http://java.sun.com/security/JCE1.2/spec/apidoc/javax/crypto/Mac.html>
13
+
14
+ module HMAC
15
+ class Base
16
+ def initialize(algorithm, block_size, output_length, key)
17
+ @algorithm = algorithm
18
+ @block_size = block_size
19
+ @output_length = output_length
20
+ @status = STATUS_UNDEFINED
21
+ @key_xor_ipad = ""
22
+ @key_xor_opad = ""
23
+ set_key(key) unless key.nil?
24
+ end
25
+
26
+ private
27
+
28
+ def check_status
29
+ return if @status == STATUS_INITIALIZED
30
+
31
+ raise "The underlying hash algorithm has not yet been initialized."
32
+ end
33
+
34
+ public
35
+
36
+ def set_key(key)
37
+ # If key is longer than the block size, apply hash function
38
+ # to key and use the result as a real key.
39
+ key = @algorithm.digest(key) if key.size > @block_size
40
+ key_xor_ipad = "\x36" * @block_size
41
+ key_xor_opad = "\x5C" * @block_size
42
+ for i in 0..key.size - 1
43
+ key_xor_ipad[i] ^= key[i]
44
+ key_xor_opad[i] ^= key[i]
45
+ end
46
+ @key_xor_ipad = key_xor_ipad
47
+ @key_xor_opad = key_xor_opad
48
+ @md = @algorithm.new
49
+ @status = STATUS_INITIALIZED
50
+ end
51
+
52
+ def reset_key
53
+ @key_xor_ipad.gsub!(/./, "?")
54
+ @key_xor_opad.gsub!(/./, "?")
55
+ @key_xor_ipad[0..-1] = ""
56
+ @key_xor_opad[0..-1] = ""
57
+ @status = STATUS_UNDEFINED
58
+ end
59
+
60
+ def update(text)
61
+ check_status
62
+ # perform inner H
63
+ md = @algorithm.new
64
+ md.update(@key_xor_ipad)
65
+ md.update(text)
66
+ str = md.digest
67
+ # perform outer H
68
+ md = @algorithm.new
69
+ md.update(@key_xor_opad)
70
+ md.update(str)
71
+ @md = md
72
+ end
73
+ alias_method :<<, :update
74
+
75
+ def digest
76
+ check_status
77
+ @md.digest
78
+ end
79
+
80
+ def hexdigest
81
+ check_status
82
+ @md.hexdigest
83
+ end
84
+ alias_method :to_s, :hexdigest
85
+
86
+ # These two class methods below are safer than using above
87
+ # instance methods combinatorially because an instance will have
88
+ # held a key even if it's no longer in use.
89
+ def self.digest(key, text)
90
+ hmac = new(key)
91
+ hmac.update(text)
92
+ hmac.digest
93
+ ensure
94
+ hmac.reset_key
95
+ end
96
+
97
+ def self.hexdigest(key, text)
98
+ hmac = new(key)
99
+ hmac.update(text)
100
+ hmac.hexdigest
101
+ ensure
102
+ hmac.reset_key
103
+ end
104
+
105
+ private_class_method :new, :digest, :hexdigest
106
+ end
107
+
108
+ STATUS_UNDEFINED = 0
109
+ STATUS_INITIALIZED = 1
110
+ end
data/lib/hmac/sha1.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "hmac/hmac"
2
+ require "digest/sha1"
3
+
4
+ module HMAC
5
+ class SHA1 < Base
6
+ def initialize(key = nil)
7
+ super(Digest::SHA1, 64, 20, key)
8
+ end
9
+ public_class_method :new, :digest, :hexdigest
10
+ end
11
+ end
data/lib/hmac/sha2.rb ADDED
@@ -0,0 +1,25 @@
1
+ require "hmac/hmac"
2
+ require "digest/sha2"
3
+
4
+ module HMAC
5
+ class SHA256 < Base
6
+ def initialize(key = nil)
7
+ super(Digest::SHA256, 64, 32, key)
8
+ end
9
+ public_class_method :new, :digest, :hexdigest
10
+ end
11
+
12
+ class SHA384 < Base
13
+ def initialize(key = nil)
14
+ super(Digest::SHA384, 128, 48, key)
15
+ end
16
+ public_class_method :new, :digest, :hexdigest
17
+ end
18
+
19
+ class SHA512 < Base
20
+ def initialize(key = nil)
21
+ super(Digest::SHA512, 128, 64, key)
22
+ end
23
+ public_class_method :new, :digest, :hexdigest
24
+ end
25
+ end
@@ -0,0 +1,246 @@
1
+ require_relative "kvform"
2
+ require_relative "util"
3
+ require_relative "cryptutil"
4
+ require_relative "message"
5
+
6
+ module OpenID
7
+ def self.get_secret_size(assoc_type)
8
+ if assoc_type == "HMAC-SHA1"
9
+ 20
10
+ elsif assoc_type == "HMAC-SHA256"
11
+ 32
12
+ else
13
+ raise ArgumentError("Unsupported association type: #{assoc_type}")
14
+ end
15
+ end
16
+
17
+ # An Association holds the shared secret between a relying party and
18
+ # an OpenID provider.
19
+ class Association
20
+ attr_reader :handle, :secret, :issued, :lifetime, :assoc_type
21
+
22
+ FIELD_ORDER =
23
+ %i[version handle secret issued lifetime assoc_type]
24
+
25
+ # Load a serialized Association
26
+ def self.deserialize(serialized)
27
+ parsed = Util.kv_to_seq(serialized)
28
+ parsed_fields = parsed.map { |k, _v| k.to_sym }
29
+ if parsed_fields != FIELD_ORDER
30
+ raise ProtocolError, "Unexpected fields in serialized association " \
31
+ "(Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})"
32
+ end
33
+ version, handle, secret64, issued_s, lifetime_s, assoc_type =
34
+ parsed.map { |_field, value| value }
35
+ if version != "2"
36
+ raise ProtocolError, "Attempted to deserialize unsupported version " \
37
+ "(#{parsed[0][1].inspect})"
38
+ end
39
+
40
+ new(
41
+ handle,
42
+ Util.from_base64(secret64),
43
+ Time.at(issued_s.to_i),
44
+ lifetime_s.to_i,
45
+ assoc_type,
46
+ )
47
+ end
48
+
49
+ # Create an Association with an issued time of now
50
+ def self.from_expires_in(expires_in, handle, secret, assoc_type)
51
+ issued = Time.now
52
+ new(handle, secret, issued, expires_in, assoc_type)
53
+ end
54
+
55
+ def initialize(handle, secret, issued, lifetime, assoc_type)
56
+ @handle = handle
57
+ @secret = secret
58
+ @issued = issued
59
+ @lifetime = lifetime
60
+ @assoc_type = assoc_type
61
+ end
62
+
63
+ # Serialize the association to a form that's consistent across
64
+ # JanRain OpenID libraries.
65
+ def serialize
66
+ data = {
67
+ version: "2",
68
+ handle: handle,
69
+ secret: Util.to_base64(secret),
70
+ issued: issued.to_i.to_s,
71
+ lifetime: lifetime.to_i.to_s,
72
+ assoc_type: assoc_type,
73
+ }
74
+
75
+ Util.truthy_assert(data.length == FIELD_ORDER.length)
76
+
77
+ pairs = FIELD_ORDER.map { |field| [field.to_s, data[field]] }
78
+ Util.seq_to_kv(pairs, true)
79
+ end
80
+
81
+ # The number of seconds until this association expires
82
+ def expires_in(now = nil)
83
+ now = if now.nil?
84
+ Time.now.to_i
85
+ else
86
+ now.to_i
87
+ end
88
+ time_diff = (issued.to_i + lifetime) - now
89
+ return 0 if time_diff < 0
90
+
91
+ time_diff
92
+ end
93
+
94
+ # Generate a signature for a sequence of [key, value] pairs
95
+ def sign(pairs)
96
+ kv = Util.seq_to_kv(pairs)
97
+ case assoc_type
98
+ when "HMAC-SHA1"
99
+ CryptUtil.hmac_sha1(@secret, kv)
100
+ when "HMAC-SHA256"
101
+ CryptUtil.hmac_sha256(@secret, kv)
102
+ else
103
+ raise ProtocolError, "Association has unknown type: " \
104
+ "#{assoc_type.inspect}"
105
+ end
106
+ end
107
+
108
+ # Generate the list of pairs that form the signed elements of the
109
+ # given message
110
+ def make_pairs(message)
111
+ signed = message.get_arg(OPENID_NS, "signed")
112
+ raise ProtocolError, "Missing signed list" if signed.nil?
113
+
114
+ signed_fields = signed.split(",", -1)
115
+ data = message.to_post_args
116
+ signed_fields.map { |field| [field, data.fetch("openid." + field, "")] }
117
+ end
118
+
119
+ # Return whether the message's signature passes
120
+ def check_message_signature(message)
121
+ message_sig = message.get_arg(OPENID_NS, "sig")
122
+ raise ProtocolError, "#{message} has no sig." if message_sig.nil?
123
+
124
+ calculated_sig = get_message_signature(message)
125
+ CryptUtil.const_eq(calculated_sig, message_sig)
126
+ end
127
+
128
+ # Get the signature for this message
129
+ def get_message_signature(message)
130
+ Util.to_base64(sign(make_pairs(message)))
131
+ end
132
+
133
+ def ==(other)
134
+ (other.class == self.class and
135
+ other.handle == handle and
136
+ other.secret == secret and
137
+
138
+ # The internals of the time objects seemed to differ
139
+ # in an opaque way when serializing/unserializing.
140
+ # I don't think this will be a problem.
141
+ other.issued.to_i == issued.to_i and
142
+
143
+ other.lifetime == lifetime and
144
+ other.assoc_type == assoc_type)
145
+ end
146
+
147
+ # Add a signature (and a signed list) to a message.
148
+ def sign_message(message)
149
+ if message.has_key?(OPENID_NS, "sig") or
150
+ message.has_key?(OPENID_NS, "signed")
151
+ raise ArgumentError, "Message already has signed list or signature"
152
+ end
153
+
154
+ extant_handle = message.get_arg(OPENID_NS, "assoc_handle")
155
+ raise ArgumentError, "Message has a different association handle" if extant_handle and extant_handle != handle
156
+
157
+ signed_message = message.copy
158
+ signed_message.set_arg(OPENID_NS, "assoc_handle", handle)
159
+ message_keys = signed_message.to_post_args.keys
160
+
161
+ signed_list = []
162
+ message_keys.each do |k|
163
+ signed_list << k[7..-1] if k.start_with?("openid.")
164
+ end
165
+
166
+ signed_list << "signed"
167
+ signed_list.sort!
168
+
169
+ signed_message.set_arg(OPENID_NS, "signed", signed_list.join(","))
170
+ sig = get_message_signature(signed_message)
171
+ signed_message.set_arg(OPENID_NS, "sig", sig)
172
+ signed_message
173
+ end
174
+ end
175
+
176
+ class AssociationNegotiator
177
+ attr_reader :allowed_types
178
+
179
+ def self.get_session_types(assoc_type)
180
+ case assoc_type
181
+ when "HMAC-SHA1"
182
+ %w[DH-SHA1 no-encryption]
183
+ when "HMAC-SHA256"
184
+ %w[DH-SHA256 no-encryption]
185
+ else
186
+ raise ProtocolError, "Unknown association type #{assoc_type.inspect}"
187
+ end
188
+ end
189
+
190
+ def self.check_session_type(assoc_type, session_type)
191
+ return if get_session_types(assoc_type).include?(session_type)
192
+
193
+ raise ProtocolError, "Session type #{session_type.inspect} not " \
194
+ "valid for association type #{assoc_type.inspect}"
195
+ end
196
+
197
+ def initialize(allowed_types)
198
+ self.allowed_types = (allowed_types)
199
+ end
200
+
201
+ def copy
202
+ Marshal.load(Marshal.dump(self))
203
+ end
204
+
205
+ def allowed_types=(allowed_types)
206
+ allowed_types.each do |assoc_type, session_type|
207
+ self.class.check_session_type(assoc_type, session_type)
208
+ end
209
+ @allowed_types = allowed_types
210
+ end
211
+
212
+ def add_allowed_type(assoc_type, session_type = nil)
213
+ if session_type.nil?
214
+ session_types = self.class.get_session_types(assoc_type)
215
+ else
216
+ self.class.check_session_type(assoc_type, session_type)
217
+ session_types = [session_type]
218
+ end
219
+ for session_type in session_types do
220
+ @allowed_types << [assoc_type, session_type]
221
+ end
222
+ end
223
+
224
+ def allowed?(assoc_type, session_type)
225
+ @allowed_types.include?([assoc_type, session_type])
226
+ end
227
+
228
+ def get_allowed_type
229
+ @allowed_types.empty? ? nil : @allowed_types[0]
230
+ end
231
+ end
232
+
233
+ DefaultNegotiator =
234
+ AssociationNegotiator.new([
235
+ %w[HMAC-SHA1 DH-SHA1],
236
+ %w[HMAC-SHA1 no-encryption],
237
+ %w[HMAC-SHA256 DH-SHA256],
238
+ %w[HMAC-SHA256 no-encryption],
239
+ ])
240
+
241
+ EncryptedNegotiator =
242
+ AssociationNegotiator.new([
243
+ %w[HMAC-SHA1 DH-SHA1],
244
+ %w[HMAC-SHA256 DH-SHA256],
245
+ ])
246
+ end