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.
@@ -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
- assert_equal DEFAULTS.issuer, root.elements["saml:Issuer"].text
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.elements["saml:Issuer"].text
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.elements["saml:Issuer"].text
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.elements["saml:Issuer"].text
47
- name_id = root.elements["saml:Assertion/saml:Subject/saml:NameID"]
48
- assert_equal email, name_id.text
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
- statement = root.elements["saml:Assertion/saml:AttributeStatement"]
64
- email_element = statement.elements["saml:Attribute[@Name='email']"]
65
- role_element = statement.elements["saml:Attribute[@Name='role']"]
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.elements["saml:AttributeValue"].text
68
- assert_equal role, role_element.elements["saml:AttributeValue"].text
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
- statement = root.elements["saml:Assertion/saml:AttributeStatement"]
80
- email_element = statement.elements["saml:Attribute[@Name='email']"]
81
- assert_equal email, email_element.elements["saml:AttributeValue"].text
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.namespace("saml")
113
+ assert_equal SAML_ASSERTION_NS, @root.namespaces["xmlns:saml"]
102
114
  end
103
115
 
104
116
  def test_root_id
105
- assert @root.attributes["ID"].start_with?("_")
117
+ assert @root["ID"].start_with?("_")
106
118
  end
107
119
 
108
120
  def test_root_version
109
- assert_equal "2.0", @root.attributes["Version"]
121
+ assert_equal "2.0", @root["Version"]
110
122
  end
111
123
 
112
124
  def test_root_issue_instant
113
- instant = Time.iso8601(@root.attributes["IssueInstant"])
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.attributes["Destination"]
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.attributes["Destination"]
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.attributes["InResponseTo"]
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.attributes["InResponseTo"]
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.attributes["Destination"]
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.attributes["InResponseTo"]
158
+ assert_nil root["InResponseTo"]
147
159
  end
148
160
 
149
161
  def test_issuer
150
- issuer = @root.elements["saml:Issuer"]
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.elements["saml:Issuer"]
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.elements["samlp:Status"]
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.elements["samlp:Status/samlp:StatusCode"]
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.attributes["Value"]
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.elements["saml:Assertion"]
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.elements["saml:Assertion"]
185
- assert assertion.attributes["ID"].start_with?("_")
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
- assertion = root.elements["saml:Assertion"]
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
- assertion = root.elements["saml:Assertion"]
202
- name_id = assertion.elements["saml:Subject/saml:NameID"]
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.elements["ds:Signature"]
208
- assert_nil @root.elements["saml:Assertion/ds:Signature"]
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.generate).document.root
213
- refute_nil root.elements["ds:Signature"]
214
- refute_nil root.elements["saml:Assertion/ds:Signature"]
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.elements["saml:EncryptedAssertion"]
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.generate).document.root
223
- ea = root.elements["saml:EncryptedAssertion"]
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.generate).document.root
230
- assert_nil root.elements["saml:Assertion"]
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.generate).document.root
235
- ed = root.elements["saml:EncryptedAssertion/xenc:EncryptedData"]
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
- @certificate = Certificate.generate
9
- @assertion = Assertion.new.document
10
- @element = @assertion.root
11
- Signature.new(@element, @certificate).sign!
12
- @signature = @element.elements["ds:Signature"]
13
- @signed_info = @signature.elements["ds:SignedInfo"]
14
- @reference = @signed_info.elements["ds:Reference"]
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.elements.to_a
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.elements.to_a
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.elements["ds:CanonicalizationMethod"]
52
+ cm = @signed_info.at_xpath("ds:CanonicalizationMethod", NS)
45
53
  assert_equal "CanonicalizationMethod", cm.name
46
- assert_equal EXC_C14N, cm.attributes["Algorithm"]
54
+ assert_equal EXC_C14N, cm["Algorithm"]
47
55
  end
48
56
 
49
57
  def test_signature_method
50
- sm = @signed_info.elements["ds:SignatureMethod"]
58
+ sm = @signed_info.at_xpath("ds:SignatureMethod", NS)
51
59
  assert_equal "SignatureMethod", sm.name
52
- assert_equal RSA_SHA256, sm.attributes["Algorithm"]
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
- element_id = @element.attributes["ID"]
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.elements["ds:Transforms"]
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.elements["ds:Transforms"]
73
- transform = transforms.elements.to_a("ds:Transform")[0]
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.elements["ds:Transforms"]
79
- transform = transforms.elements.to_a("ds:Transform")[1]
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.elements["ds:DigestMethod"]
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.elements["ds:DigestMethod"]
91
- assert_equal SHA256_DIGEST, digest_method.attributes["Algorithm"]
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.elements["ds:DigestValue"]
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.elements["ds:DigestValue"]
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.elements["ds:SignatureValue"]
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.elements["ds:SignatureValue"]
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.elements["ds:KeyInfo"]
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.elements["ds:KeyInfo/ds:X509Data"]
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
- cert = @signature.elements["ds:KeyInfo/ds:X509Data/ds:X509Certificate"]
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.alpha2
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-01-27 00:00:00.000000000 Z
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: ostruct
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: rexml
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: 1.3.1
161
+ version: '0'
130
162
  requirements: []
131
163
  rubygems_version: 3.4.19
132
164
  signing_key: