lyrebird 1.0.0.alpha2 → 1.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 +4 -4
- data/LICENSE.md +22 -0
- data/README.md +35 -27
- data/lib/lyrebird/assertion.rb +82 -46
- data/lib/lyrebird/certificate.rb +30 -21
- data/lib/lyrebird/defaults.rb +1 -1
- data/lib/lyrebird/encryption.rb +54 -33
- data/lib/lyrebird/response.rb +52 -25
- data/lib/lyrebird/signature.rb +86 -38
- data/lib/lyrebird/version.rb +1 -1
- data/lib/lyrebird.rb +16 -1
- data/lyrebird.gemspec +8 -1
- data/test/lyrebird/assertion_test.rb +115 -96
- data/test/lyrebird/certificate_test.rb +70 -37
- data/test/lyrebird/defaults_test.rb +47 -0
- data/test/lyrebird/encryption_test.rb +48 -35
- data/test/lyrebird/id_test.rb +7 -0
- data/test/lyrebird/integration_test.rb +243 -0
- data/test/lyrebird/namespaces_test.rb +63 -0
- data/test/lyrebird/response_test.rb +67 -57
- data/test/lyrebird/signature_test.rb +77 -43
- metadata +40 -8
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
module Lyrebird
|
|
6
|
+
class NamespacesTest < Minitest::Test
|
|
7
|
+
def test_saml_assertion_ns
|
|
8
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:assertion", SAML_ASSERTION_NS
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_saml_protocol_ns
|
|
12
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", SAML_PROTOCOL_NS
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_xmldsig_ns
|
|
16
|
+
assert_equal "http://www.w3.org/2000/09/xmldsig#", XMLDSIG_NS
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_enveloped_sig
|
|
20
|
+
expected = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
|
21
|
+
assert_equal expected, ENVELOPED_SIG
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_exc_c14n
|
|
25
|
+
assert_equal "http://www.w3.org/2001/10/xml-exc-c14n#", EXC_C14N
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_sha256_digest
|
|
29
|
+
assert_equal "http://www.w3.org/2001/04/xmlenc#sha256", SHA256_DIGEST
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_rsa_sha256
|
|
33
|
+
expected = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
|
34
|
+
assert_equal expected, RSA_SHA256
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_cm_bearer
|
|
38
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:cm:bearer", CM_BEARER
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_attr_name_format
|
|
42
|
+
expected = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
|
|
43
|
+
assert_equal expected, ATTR_NAME_FORMAT
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_status_success
|
|
47
|
+
assert_equal "urn:oasis:names:tc:SAML:2.0:status:Success", STATUS_SUCCESS
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_xmlenc_ns
|
|
51
|
+
assert_equal "http://www.w3.org/2001/04/xmlenc#", XMLENC_NS
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_aes256_cbc
|
|
55
|
+
assert_equal "http://www.w3.org/2001/04/xmlenc#aes256-cbc", AES256_CBC
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_rsa_oaep
|
|
59
|
+
expected = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
|
60
|
+
assert_equal expected, RSA_OAEP
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -4,6 +4,13 @@ require "test_helper"
|
|
|
4
4
|
|
|
5
5
|
module Lyrebird
|
|
6
6
|
class ResponseTest < Minitest::Test
|
|
7
|
+
NAMESPACES = {
|
|
8
|
+
"saml" => SAML_ASSERTION_NS,
|
|
9
|
+
"samlp" => SAML_PROTOCOL_NS,
|
|
10
|
+
"ds" => XMLDSIG_NS,
|
|
11
|
+
"xenc" => XMLENC_NS
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
7
14
|
def setup
|
|
8
15
|
@response = Response.new
|
|
9
16
|
@root = @response.document.root
|
|
@@ -12,7 +19,8 @@ module Lyrebird
|
|
|
12
19
|
def test_build_with_defaults
|
|
13
20
|
response = Response.build
|
|
14
21
|
root = response.document.root
|
|
15
|
-
|
|
22
|
+
issuer = root.at_xpath("saml:Issuer", NAMESPACES)
|
|
23
|
+
assert_equal DEFAULTS.issuer, issuer.text
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def test_build_with_kwargs
|
|
@@ -20,7 +28,7 @@ module Lyrebird
|
|
|
20
28
|
refute_equal issuer, DEFAULTS.issuer
|
|
21
29
|
response = Response.build(issuer: issuer)
|
|
22
30
|
root = response.document.root
|
|
23
|
-
assert_equal issuer, root.
|
|
31
|
+
assert_equal issuer, root.at_xpath("saml:Issuer", NAMESPACES).text
|
|
24
32
|
end
|
|
25
33
|
|
|
26
34
|
def test_build_with_block
|
|
@@ -28,7 +36,7 @@ module Lyrebird
|
|
|
28
36
|
refute_equal issuer, DEFAULTS.issuer
|
|
29
37
|
response = Response.build { |r| r.issuer = issuer }
|
|
30
38
|
root = response.document.root
|
|
31
|
-
assert_equal issuer, root.
|
|
39
|
+
assert_equal issuer, root.at_xpath("saml:Issuer", NAMESPACES).text
|
|
32
40
|
end
|
|
33
41
|
|
|
34
42
|
def test_build_with_kwargs_and_block
|
|
@@ -43,9 +51,9 @@ module Lyrebird
|
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
root = response.document.root
|
|
46
|
-
assert_equal issuer, root.
|
|
47
|
-
|
|
48
|
-
assert_equal email,
|
|
54
|
+
assert_equal issuer, root.at_xpath("saml:Issuer", NAMESPACES).text
|
|
55
|
+
xpath = "saml:Assertion/saml:Subject/saml:NameID"
|
|
56
|
+
assert_equal email, root.at_xpath(xpath, NAMESPACES).text
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
def test_build_with_attributes_block
|
|
@@ -60,12 +68,15 @@ module Lyrebird
|
|
|
60
68
|
end
|
|
61
69
|
|
|
62
70
|
root = response.document.root
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
statement_xpath = "saml:Assertion/saml:AttributeStatement"
|
|
72
|
+
statement = root.at_xpath(statement_xpath, NAMESPACES)
|
|
73
|
+
email_xpath = "saml:Attribute[@Name='email']/saml:AttributeValue"
|
|
74
|
+
role_xpath = "saml:Attribute[@Name='role']/saml:AttributeValue"
|
|
75
|
+
email_element = statement.at_xpath(email_xpath, NAMESPACES)
|
|
76
|
+
role_element = statement.at_xpath(role_xpath, NAMESPACES)
|
|
66
77
|
|
|
67
|
-
assert_equal email, email_element.
|
|
68
|
-
assert_equal role, role_element.
|
|
78
|
+
assert_equal email, email_element.text
|
|
79
|
+
assert_equal role, role_element.text
|
|
69
80
|
end
|
|
70
81
|
|
|
71
82
|
def test_build_with_attributes_hash
|
|
@@ -76,9 +87,10 @@ module Lyrebird
|
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
root = response.document.root
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
statement_xpath = "saml:Assertion/saml:AttributeStatement"
|
|
91
|
+
attr_xpath = "saml:Attribute[@Name='email']/saml:AttributeValue"
|
|
92
|
+
statement = root.at_xpath(statement_xpath, NAMESPACES)
|
|
93
|
+
assert_equal email, statement.at_xpath(attr_xpath, NAMESPACES).text
|
|
82
94
|
end
|
|
83
95
|
|
|
84
96
|
def test_mimic_returns_base64
|
|
@@ -90,66 +102,66 @@ module Lyrebird
|
|
|
90
102
|
|
|
91
103
|
def test_root_name
|
|
92
104
|
assert_equal "Response", @root.name
|
|
93
|
-
assert_equal "samlp", @root.prefix
|
|
105
|
+
assert_equal "samlp", @root.namespace.prefix
|
|
94
106
|
end
|
|
95
107
|
|
|
96
108
|
def test_root_namespace
|
|
97
|
-
assert_equal SAML_PROTOCOL_NS, @root.namespace
|
|
109
|
+
assert_equal SAML_PROTOCOL_NS, @root.namespace.href
|
|
98
110
|
end
|
|
99
111
|
|
|
100
112
|
def test_saml_namespace_declared
|
|
101
|
-
assert_equal SAML_ASSERTION_NS, @root.
|
|
113
|
+
assert_equal SAML_ASSERTION_NS, @root.namespaces["xmlns:saml"]
|
|
102
114
|
end
|
|
103
115
|
|
|
104
116
|
def test_root_id
|
|
105
|
-
assert @root
|
|
117
|
+
assert @root["ID"].start_with?("_")
|
|
106
118
|
end
|
|
107
119
|
|
|
108
120
|
def test_root_version
|
|
109
|
-
assert_equal "2.0", @root
|
|
121
|
+
assert_equal "2.0", @root["Version"]
|
|
110
122
|
end
|
|
111
123
|
|
|
112
124
|
def test_root_issue_instant
|
|
113
|
-
instant = Time.iso8601(@root
|
|
125
|
+
instant = Time.iso8601(@root["IssueInstant"])
|
|
114
126
|
assert_in_delta Time.now.to_i, instant.to_i, 1
|
|
115
127
|
end
|
|
116
128
|
|
|
117
129
|
def test_destination_default
|
|
118
|
-
assert_equal DEFAULTS.recipient, @root
|
|
130
|
+
assert_equal DEFAULTS.recipient, @root["Destination"]
|
|
119
131
|
end
|
|
120
132
|
|
|
121
133
|
def test_destination_override
|
|
122
134
|
destination = "https://test.example.com/acs"
|
|
123
135
|
refute_equal destination, DEFAULTS.recipient
|
|
124
136
|
root = Response.new(destination: destination).document.root
|
|
125
|
-
assert_equal destination, root
|
|
137
|
+
assert_equal destination, root["Destination"]
|
|
126
138
|
end
|
|
127
139
|
|
|
128
140
|
def test_in_response_to_default
|
|
129
|
-
assert_equal DEFAULTS.in_response_to, @root
|
|
141
|
+
assert_equal DEFAULTS.in_response_to, @root["InResponseTo"]
|
|
130
142
|
end
|
|
131
143
|
|
|
132
144
|
def test_in_response_to_override
|
|
133
145
|
in_response_to = "_test_request"
|
|
134
146
|
refute_equal in_response_to, DEFAULTS.in_response_to
|
|
135
147
|
root = Response.new(in_response_to: in_response_to).document.root
|
|
136
|
-
assert_equal in_response_to, root
|
|
148
|
+
assert_equal in_response_to, root["InResponseTo"]
|
|
137
149
|
end
|
|
138
150
|
|
|
139
151
|
def test_destination_omitted_when_nil
|
|
140
152
|
root = Response.new(destination: nil).document.root
|
|
141
|
-
assert_nil root
|
|
153
|
+
assert_nil root["Destination"]
|
|
142
154
|
end
|
|
143
155
|
|
|
144
156
|
def test_in_response_to_omitted_when_nil
|
|
145
157
|
root = Response.new(in_response_to: nil).document.root
|
|
146
|
-
assert_nil root
|
|
158
|
+
assert_nil root["InResponseTo"]
|
|
147
159
|
end
|
|
148
160
|
|
|
149
161
|
def test_issuer
|
|
150
|
-
issuer = @root.
|
|
162
|
+
issuer = @root.at_xpath("saml:Issuer", NAMESPACES)
|
|
151
163
|
assert_equal "Issuer", issuer.name
|
|
152
|
-
assert_equal "saml", issuer.prefix
|
|
164
|
+
assert_equal "saml", issuer.namespace.prefix
|
|
153
165
|
assert_equal DEFAULTS.issuer, issuer.text
|
|
154
166
|
end
|
|
155
167
|
|
|
@@ -157,40 +169,39 @@ module Lyrebird
|
|
|
157
169
|
url = "https://test.idp.example.com"
|
|
158
170
|
refute_equal url, DEFAULTS.issuer
|
|
159
171
|
root = Response.new(issuer: url).document.root
|
|
160
|
-
issuer = root.
|
|
172
|
+
issuer = root.at_xpath("saml:Issuer", NAMESPACES)
|
|
161
173
|
assert_equal url, issuer.text
|
|
162
174
|
end
|
|
163
175
|
|
|
164
176
|
def test_status
|
|
165
|
-
status = @root.
|
|
177
|
+
status = @root.at_xpath("samlp:Status", NAMESPACES)
|
|
166
178
|
assert_equal "Status", status.name
|
|
167
|
-
assert_equal "samlp", status.prefix
|
|
179
|
+
assert_equal "samlp", status.namespace.prefix
|
|
168
180
|
end
|
|
169
181
|
|
|
170
182
|
def test_status_code
|
|
171
|
-
status_code = @root.
|
|
183
|
+
status_code = @root.at_xpath("samlp:Status/samlp:StatusCode", NAMESPACES)
|
|
172
184
|
assert_equal "StatusCode", status_code.name
|
|
173
|
-
assert_equal "samlp", status_code.prefix
|
|
174
|
-
assert_equal STATUS_SUCCESS, status_code
|
|
185
|
+
assert_equal "samlp", status_code.namespace.prefix
|
|
186
|
+
assert_equal STATUS_SUCCESS, status_code["Value"]
|
|
175
187
|
end
|
|
176
188
|
|
|
177
189
|
def test_assertion_embedded
|
|
178
|
-
assertion = @root.
|
|
190
|
+
assertion = @root.at_xpath("saml:Assertion", NAMESPACES)
|
|
179
191
|
assert_equal "Assertion", assertion.name
|
|
180
|
-
assert_equal "saml", assertion.prefix
|
|
192
|
+
assert_equal "saml", assertion.namespace.prefix
|
|
181
193
|
end
|
|
182
194
|
|
|
183
195
|
def test_assertion_has_id
|
|
184
|
-
assertion = @root.
|
|
185
|
-
assert assertion
|
|
196
|
+
assertion = @root.at_xpath("saml:Assertion", NAMESPACES)
|
|
197
|
+
assert assertion["ID"].start_with?("_")
|
|
186
198
|
end
|
|
187
199
|
|
|
188
200
|
def test_assertion_inherits_issuer
|
|
189
201
|
url = "https://test.idp.example.com"
|
|
190
202
|
refute_equal url, DEFAULTS.issuer
|
|
191
203
|
root = Response.new(issuer: url).document.root
|
|
192
|
-
|
|
193
|
-
issuer = assertion.elements["saml:Issuer"]
|
|
204
|
+
issuer = root.at_xpath("saml:Assertion/saml:Issuer", NAMESPACES)
|
|
194
205
|
assert_equal url, issuer.text
|
|
195
206
|
end
|
|
196
207
|
|
|
@@ -198,43 +209,42 @@ module Lyrebird
|
|
|
198
209
|
email = "test@example.com"
|
|
199
210
|
refute_equal email, DEFAULTS.name_id
|
|
200
211
|
root = Response.new(name_id: email).document.root
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
assert_equal email, name_id.text
|
|
212
|
+
xpath = "saml:Assertion/saml:Subject/saml:NameID"
|
|
213
|
+
assert_equal email, root.at_xpath(xpath, NAMESPACES).text
|
|
204
214
|
end
|
|
205
215
|
|
|
206
216
|
def test_unsigned_by_default
|
|
207
|
-
assert_nil @root.
|
|
208
|
-
assert_nil @root.
|
|
217
|
+
assert_nil @root.at_xpath("ds:Signature", NAMESPACES)
|
|
218
|
+
assert_nil @root.at_xpath("saml:Assertion/ds:Signature", NAMESPACES)
|
|
209
219
|
end
|
|
210
220
|
|
|
211
221
|
def test_sign_with_signs_response_and_assertion
|
|
212
|
-
root = Response.new(sign_with: Certificate.
|
|
213
|
-
refute_nil root.
|
|
214
|
-
refute_nil root.
|
|
222
|
+
root = Response.new(sign_with: Certificate.build).document.root
|
|
223
|
+
refute_nil root.at_xpath("ds:Signature", NAMESPACES)
|
|
224
|
+
refute_nil root.at_xpath("saml:Assertion/ds:Signature", NAMESPACES)
|
|
215
225
|
end
|
|
216
226
|
|
|
217
227
|
def test_not_encrypted_by_default
|
|
218
|
-
assert_nil @root.
|
|
228
|
+
assert_nil @root.at_xpath("saml:EncryptedAssertion", NAMESPACES)
|
|
219
229
|
end
|
|
220
230
|
|
|
221
231
|
def test_encrypt_with_creates_encrypted_assertion
|
|
222
|
-
root = Response.new(encrypt_with: Certificate.
|
|
223
|
-
ea = root.
|
|
232
|
+
root = Response.new(encrypt_with: Certificate.build).document.root
|
|
233
|
+
ea = root.at_xpath("saml:EncryptedAssertion", NAMESPACES)
|
|
224
234
|
assert_equal "EncryptedAssertion", ea.name
|
|
225
|
-
assert_equal "saml", ea.prefix
|
|
235
|
+
assert_equal "saml", ea.namespace.prefix
|
|
226
236
|
end
|
|
227
237
|
|
|
228
238
|
def test_encrypt_with_removes_plain_assertion
|
|
229
|
-
root = Response.new(encrypt_with: Certificate.
|
|
230
|
-
assert_nil root.
|
|
239
|
+
root = Response.new(encrypt_with: Certificate.build).document.root
|
|
240
|
+
assert_nil root.at_xpath("saml:Assertion", NAMESPACES)
|
|
231
241
|
end
|
|
232
242
|
|
|
233
243
|
def test_encrypt_with_contains_encrypted_data
|
|
234
|
-
root = Response.new(encrypt_with: Certificate.
|
|
235
|
-
|
|
244
|
+
root = Response.new(encrypt_with: Certificate.build).document.root
|
|
245
|
+
xpath = "saml:EncryptedAssertion/xenc:EncryptedData"
|
|
246
|
+
ed = root.at_xpath(xpath, NAMESPACES)
|
|
236
247
|
assert_equal "EncryptedData", ed.name
|
|
237
248
|
end
|
|
238
|
-
|
|
239
249
|
end
|
|
240
250
|
end
|
|
@@ -4,18 +4,26 @@ require "test_helper"
|
|
|
4
4
|
|
|
5
5
|
module Lyrebird
|
|
6
6
|
class SignatureTest < Minitest::Test
|
|
7
|
+
NS = { "ds" => XMLDSIG_NS, "saml" => SAML_ASSERTION_NS }.freeze
|
|
8
|
+
|
|
7
9
|
def setup
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
unless defined?(@@certificate)
|
|
11
|
+
@@certificate = Certificate.build
|
|
12
|
+
@@assertion = Assertion.new.document
|
|
13
|
+
@@element = @@assertion.root
|
|
14
|
+
Signature.new(@@element, @@certificate).sign!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@certificate = @@certificate
|
|
18
|
+
@assertion = @@assertion
|
|
19
|
+
@element = @@element
|
|
20
|
+
@signature = @element.at_xpath("ds:Signature", NS)
|
|
21
|
+
@signed_info = @signature.at_xpath("ds:SignedInfo", NS)
|
|
22
|
+
@reference = @signed_info.at_xpath("ds:Reference", NS)
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
def test_sign_inserts_after_issuer
|
|
18
|
-
children = @element.
|
|
26
|
+
children = @element.element_children
|
|
19
27
|
issuer_index = children.index { |e| e.name == "Issuer" }
|
|
20
28
|
signature_index = children.index { |e| e.name == "Signature" }
|
|
21
29
|
assert_equal issuer_index + 1, signature_index
|
|
@@ -23,12 +31,12 @@ module Lyrebird
|
|
|
23
31
|
|
|
24
32
|
def test_signature_element
|
|
25
33
|
assert_equal "Signature", @signature.name
|
|
26
|
-
assert_equal "ds", @signature.prefix
|
|
27
|
-
assert_equal XMLDSIG_NS, @signature.namespace
|
|
34
|
+
assert_equal "ds", @signature.namespace.prefix
|
|
35
|
+
assert_equal XMLDSIG_NS, @signature.namespace.href
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
def test_signature_element_children
|
|
31
|
-
children = @signature.
|
|
39
|
+
children = @signature.element_children
|
|
32
40
|
assert_equal 3, children.size
|
|
33
41
|
assert_equal "SignedInfo", children[0].name
|
|
34
42
|
assert_equal "SignatureValue", children[1].name
|
|
@@ -37,101 +45,127 @@ module Lyrebird
|
|
|
37
45
|
|
|
38
46
|
def test_signed_info_element
|
|
39
47
|
assert_equal "SignedInfo", @signed_info.name
|
|
40
|
-
assert_equal "ds", @signed_info.prefix
|
|
48
|
+
assert_equal "ds", @signed_info.namespace.prefix
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
def test_canonicalization_method
|
|
44
|
-
cm = @signed_info.
|
|
52
|
+
cm = @signed_info.at_xpath("ds:CanonicalizationMethod", NS)
|
|
45
53
|
assert_equal "CanonicalizationMethod", cm.name
|
|
46
|
-
assert_equal EXC_C14N, cm
|
|
54
|
+
assert_equal EXC_C14N, cm["Algorithm"]
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
def test_signature_method
|
|
50
|
-
sm = @signed_info.
|
|
58
|
+
sm = @signed_info.at_xpath("ds:SignatureMethod", NS)
|
|
51
59
|
assert_equal "SignatureMethod", sm.name
|
|
52
|
-
assert_equal RSA_SHA256, sm
|
|
60
|
+
assert_equal RSA_SHA256, sm["Algorithm"]
|
|
53
61
|
end
|
|
54
62
|
|
|
55
63
|
def test_reference_element
|
|
56
64
|
assert_equal "Reference", @reference.name
|
|
57
|
-
assert_equal "ds", @reference.prefix
|
|
65
|
+
assert_equal "ds", @reference.namespace.prefix
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def test_reference_uri
|
|
61
|
-
|
|
62
|
-
assert_equal "##{element_id}", @reference.attributes["URI"]
|
|
69
|
+
assert_equal "##{@element["ID"]}", @reference["URI"]
|
|
63
70
|
end
|
|
64
71
|
|
|
65
72
|
def test_transforms_element
|
|
66
|
-
transforms = @reference.
|
|
73
|
+
transforms = @reference.at_xpath("ds:Transforms", NS)
|
|
67
74
|
assert_equal "Transforms", transforms.name
|
|
68
|
-
assert_equal "ds", transforms.prefix
|
|
75
|
+
assert_equal "ds", transforms.namespace.prefix
|
|
69
76
|
end
|
|
70
77
|
|
|
71
78
|
def test_enveloped_signature_transform
|
|
72
|
-
transforms = @reference.
|
|
73
|
-
|
|
74
|
-
assert_equal ENVELOPED_SIG, transform.attributes["Algorithm"]
|
|
79
|
+
transforms = @reference.xpath("ds:Transforms/ds:Transform", NS)
|
|
80
|
+
assert_equal ENVELOPED_SIG, transforms[0]["Algorithm"]
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
def test_c14n_transform
|
|
78
|
-
transforms = @reference.
|
|
79
|
-
|
|
80
|
-
assert_equal EXC_C14N, transform.attributes["Algorithm"]
|
|
84
|
+
transforms = @reference.xpath("ds:Transforms/ds:Transform", NS)
|
|
85
|
+
assert_equal EXC_C14N, transforms[1]["Algorithm"]
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
def test_digest_method_element
|
|
84
|
-
digest_method = @reference.
|
|
89
|
+
digest_method = @reference.at_xpath("ds:DigestMethod", NS)
|
|
85
90
|
assert_equal "DigestMethod", digest_method.name
|
|
86
|
-
assert_equal "ds", digest_method.prefix
|
|
91
|
+
assert_equal "ds", digest_method.namespace.prefix
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
def test_digest_method_algorithm
|
|
90
|
-
digest_method = @reference.
|
|
91
|
-
assert_equal SHA256_DIGEST, digest_method
|
|
95
|
+
digest_method = @reference.at_xpath("ds:DigestMethod", NS)
|
|
96
|
+
assert_equal SHA256_DIGEST, digest_method["Algorithm"]
|
|
92
97
|
end
|
|
93
98
|
|
|
94
99
|
def test_digest_value_element
|
|
95
|
-
digest_value = @reference.
|
|
100
|
+
digest_value = @reference.at_xpath("ds:DigestValue", NS)
|
|
96
101
|
assert_equal "DigestValue", digest_value.name
|
|
97
|
-
assert_equal "ds", digest_value.prefix
|
|
102
|
+
assert_equal "ds", digest_value.namespace.prefix
|
|
98
103
|
end
|
|
99
104
|
|
|
100
105
|
def test_digest_value_is_base64
|
|
101
|
-
digest_value = @reference.
|
|
106
|
+
digest_value = @reference.at_xpath("ds:DigestValue", NS)
|
|
102
107
|
decoded = Base64.strict_decode64(digest_value.text)
|
|
103
108
|
assert_equal 32, decoded.bytesize
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
def test_signature_value_element
|
|
107
|
-
sv = @signature.
|
|
112
|
+
sv = @signature.at_xpath("ds:SignatureValue", NS)
|
|
108
113
|
assert_equal "SignatureValue", sv.name
|
|
109
|
-
assert_equal "ds", sv.prefix
|
|
114
|
+
assert_equal "ds", sv.namespace.prefix
|
|
110
115
|
end
|
|
111
116
|
|
|
112
117
|
def test_signature_value_is_base64
|
|
113
|
-
sv = @signature.
|
|
118
|
+
sv = @signature.at_xpath("ds:SignatureValue", NS)
|
|
114
119
|
decoded = Base64.strict_decode64(sv.text)
|
|
115
120
|
assert_equal 256, decoded.bytesize
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
def test_key_info_element
|
|
119
|
-
ki = @signature.
|
|
124
|
+
ki = @signature.at_xpath("ds:KeyInfo", NS)
|
|
120
125
|
assert_equal "KeyInfo", ki.name
|
|
121
|
-
assert_equal "ds", ki.prefix
|
|
126
|
+
assert_equal "ds", ki.namespace.prefix
|
|
122
127
|
end
|
|
123
128
|
|
|
124
129
|
def test_x509_data_element
|
|
125
|
-
x509_data = @signature.
|
|
130
|
+
x509_data = @signature.at_xpath("ds:KeyInfo/ds:X509Data", NS)
|
|
126
131
|
assert_equal "X509Data", x509_data.name
|
|
127
|
-
assert_equal "ds", x509_data.prefix
|
|
132
|
+
assert_equal "ds", x509_data.namespace.prefix
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
def test_x509_certificate_element
|
|
131
|
-
|
|
136
|
+
xpath = "ds:KeyInfo/ds:X509Data/ds:X509Certificate"
|
|
137
|
+
cert = @signature.at_xpath(xpath, NS)
|
|
132
138
|
assert_equal "X509Certificate", cert.name
|
|
133
|
-
assert_equal "ds", cert.prefix
|
|
139
|
+
assert_equal "ds", cert.namespace.prefix
|
|
134
140
|
assert_equal @certificate.base64, cert.text
|
|
135
141
|
end
|
|
142
|
+
|
|
143
|
+
def test_digest_verifies
|
|
144
|
+
assertion = @assertion.dup
|
|
145
|
+
element = assertion.root
|
|
146
|
+
signature = element.at_xpath("ds:Signature", NS)
|
|
147
|
+
reference = signature.at_xpath("ds:SignedInfo/ds:Reference", NS)
|
|
148
|
+
|
|
149
|
+
signature.remove
|
|
150
|
+
c14n = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
151
|
+
canonical = element.canonicalize(c14n)
|
|
152
|
+
computed = OpenSSL::Digest::SHA256.digest(canonical)
|
|
153
|
+
|
|
154
|
+
digest_value = reference.at_xpath("ds:DigestValue", NS)
|
|
155
|
+
expected = Base64.strict_decode64(digest_value.text)
|
|
156
|
+
assert_equal expected, computed
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_signature_verifies
|
|
160
|
+
c14n = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
161
|
+
canonical = @signed_info.canonicalize(c14n)
|
|
162
|
+
|
|
163
|
+
sig_value = @signature.at_xpath("ds:SignatureValue", NS)
|
|
164
|
+
signature_bytes = Base64.strict_decode64(sig_value.text)
|
|
165
|
+
|
|
166
|
+
public_key = @certificate.x509.public_key
|
|
167
|
+
verified = public_key.verify("SHA256", signature_bytes, canonical)
|
|
168
|
+
assert verified
|
|
169
|
+
end
|
|
136
170
|
end
|
|
137
171
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lyrebird
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Josh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-02-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: nokogiri
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: ostruct
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: logger
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: minitest
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,6 +94,20 @@ dependencies:
|
|
|
80
94
|
- - ">="
|
|
81
95
|
- !ruby/object:Gem::Version
|
|
82
96
|
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: ruby-saml
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
83
111
|
description:
|
|
84
112
|
email:
|
|
85
113
|
executables: []
|
|
@@ -88,6 +116,7 @@ extra_rdoc_files: []
|
|
|
88
116
|
files:
|
|
89
117
|
- ".github/workflows/ci.yml"
|
|
90
118
|
- ".github/workflows/publish.yml"
|
|
119
|
+
- LICENSE.md
|
|
91
120
|
- README.md
|
|
92
121
|
- Rakefile
|
|
93
122
|
- lib/lyrebird.rb
|
|
@@ -107,11 +136,14 @@ files:
|
|
|
107
136
|
- test/lyrebird/defaults_test.rb
|
|
108
137
|
- test/lyrebird/encryption_test.rb
|
|
109
138
|
- test/lyrebird/id_test.rb
|
|
139
|
+
- test/lyrebird/integration_test.rb
|
|
140
|
+
- test/lyrebird/namespaces_test.rb
|
|
110
141
|
- test/lyrebird/response_test.rb
|
|
111
142
|
- test/lyrebird/signature_test.rb
|
|
112
143
|
- test/test_helper.rb
|
|
113
|
-
homepage:
|
|
114
|
-
licenses:
|
|
144
|
+
homepage: https://github.com/simplesaml/lyrebird
|
|
145
|
+
licenses:
|
|
146
|
+
- MIT
|
|
115
147
|
metadata: {}
|
|
116
148
|
post_install_message:
|
|
117
149
|
rdoc_options: []
|
|
@@ -124,9 +156,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
124
156
|
version: 3.2.0
|
|
125
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
158
|
requirements:
|
|
127
|
-
- - "
|
|
159
|
+
- - ">="
|
|
128
160
|
- !ruby/object:Gem::Version
|
|
129
|
-
version:
|
|
161
|
+
version: '0'
|
|
130
162
|
requirements: []
|
|
131
163
|
rubygems_version: 3.4.19
|
|
132
164
|
signing_key:
|