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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE.txt +210 -0
- data/README.md +81 -0
- data/SECURITY.md +15 -0
- data/lib/hmac/hmac.rb +110 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid/association.rb +246 -0
- data/lib/openid/consumer/associationmanager.rb +354 -0
- data/lib/openid/consumer/checkid_request.rb +179 -0
- data/lib/openid/consumer/discovery.rb +516 -0
- data/lib/openid/consumer/discovery_manager.rb +144 -0
- data/lib/openid/consumer/html_parse.rb +142 -0
- data/lib/openid/consumer/idres.rb +513 -0
- data/lib/openid/consumer/responses.rb +147 -0
- data/lib/openid/consumer/session.rb +36 -0
- data/lib/openid/consumer.rb +406 -0
- data/lib/openid/cryptutil.rb +112 -0
- data/lib/openid/dh.rb +84 -0
- data/lib/openid/extension.rb +38 -0
- data/lib/openid/extensions/ax.rb +552 -0
- data/lib/openid/extensions/oauth.rb +88 -0
- data/lib/openid/extensions/pape.rb +170 -0
- data/lib/openid/extensions/sreg.rb +268 -0
- data/lib/openid/extensions/ui.rb +49 -0
- data/lib/openid/fetchers.rb +277 -0
- data/lib/openid/kvform.rb +113 -0
- data/lib/openid/kvpost.rb +62 -0
- data/lib/openid/message.rb +555 -0
- data/lib/openid/protocolerror.rb +7 -0
- data/lib/openid/server.rb +1571 -0
- data/lib/openid/store/filesystem.rb +260 -0
- data/lib/openid/store/interface.rb +73 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +79 -0
- data/lib/openid/store/nonce.rb +72 -0
- data/lib/openid/trustroot.rb +597 -0
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +5 -0
- data/lib/openid/yadis/accept.rb +141 -0
- data/lib/openid/yadis/constants.rb +16 -0
- data/lib/openid/yadis/discovery.rb +151 -0
- data/lib/openid/yadis/filters.rb +192 -0
- data/lib/openid/yadis/htmltokenizer.rb +290 -0
- data/lib/openid/yadis/parsehtml.rb +50 -0
- data/lib/openid/yadis/services.rb +44 -0
- data/lib/openid/yadis/xrds.rb +160 -0
- data/lib/openid/yadis/xri.rb +86 -0
- data/lib/openid/yadis/xrires.rb +87 -0
- data/lib/openid.rb +27 -0
- data/lib/ruby-openid.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +331 -0
- 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
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
|