ruby-openid2 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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