lyrebird 0.0.0 → 1.0.0.alpha2

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,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ class Signature
5
+ def initialize(element, certificate)
6
+ @element = element
7
+ @certificate = certificate
8
+ end
9
+
10
+ def sign!
11
+ issuer = @element.elements["saml:Issuer"]
12
+ @element.insert_after(issuer, signature_element)
13
+ self
14
+ end
15
+
16
+ private
17
+
18
+ def signature_element
19
+ REXML::Element.new("ds:Signature").tap do |sig|
20
+ sig.add_namespace("ds", XMLDSIG_NS)
21
+ sig.add_element(signed_info)
22
+ sig.add_element(signature_value)
23
+ sig.add_element(key_info)
24
+ end
25
+ end
26
+
27
+ def signed_info
28
+ REXML::Element.new("ds:SignedInfo").tap do |si|
29
+ cm = si.add_element("ds:CanonicalizationMethod")
30
+ cm.add_attribute("Algorithm", EXC_C14N)
31
+ sm = si.add_element("ds:SignatureMethod")
32
+ sm.add_attribute("Algorithm", RSA_SHA256)
33
+ si.add_element(reference)
34
+ end
35
+ end
36
+
37
+ def signature_value
38
+ REXML::Element.new("ds:SignatureValue").tap do |sv|
39
+ sig = @certificate.private_key.sign("SHA256", signed_info.to_s)
40
+ sv.text = Base64.strict_encode64(sig)
41
+ end
42
+ end
43
+
44
+ def key_info
45
+ REXML::Element.new("ds:KeyInfo").tap do |ki|
46
+ x = ki.add_element("ds:X509Data")
47
+ x.add_element("ds:X509Certificate").text = @certificate.base64
48
+ end
49
+ end
50
+
51
+ def reference
52
+ REXML::Element.new("ds:Reference").tap do |ref|
53
+ ref.add_attribute("URI", "##{@element.attributes["ID"]}")
54
+ ref.add_element(transforms)
55
+ dm = ref.add_element("ds:DigestMethod")
56
+ dm.add_attribute("Algorithm", SHA256_DIGEST)
57
+ ref.add_element("ds:DigestValue").text = compute_digest(@element)
58
+ end
59
+ end
60
+
61
+ def transforms
62
+ REXML::Element.new("ds:Transforms").tap do |t|
63
+ enveloped = t.add_element("ds:Transform")
64
+ enveloped.add_attribute("Algorithm", ENVELOPED_SIG)
65
+ c14n = t.add_element("ds:Transform")
66
+ c14n.add_attribute("Algorithm", EXC_C14N)
67
+ end
68
+ end
69
+
70
+ def compute_digest(element)
71
+ digest = OpenSSL::Digest::SHA256.digest(element.to_s)
72
+ Base64.strict_encode64(digest)
73
+ end
74
+ end
75
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lyrebird
4
- VERSION = "0.0.0"
4
+ VERSION = "1.0.0.alpha2"
5
5
  end
data/lib/lyrebird.rb CHANGED
@@ -1,8 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+ require "openssl"
5
+ require "ostruct"
6
+ require "rexml"
7
+ require "securerandom"
8
+ require "time"
9
+
10
+ require_relative "lyrebird/assertion"
11
+ require_relative "lyrebird/certificate"
12
+ require_relative "lyrebird/defaults"
13
+ require_relative "lyrebird/encryption"
14
+ require_relative "lyrebird/id"
15
+ require_relative "lyrebird/namespaces"
16
+ require_relative "lyrebird/response"
17
+ require_relative "lyrebird/signature"
3
18
  require_relative "lyrebird/version"
4
19
 
5
20
  module Lyrebird
6
21
  class Error < StandardError; end
7
- # Your code goes here...
8
22
  end
data/lyrebird.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/lyrebird/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "lyrebird"
7
+ spec.version = Lyrebird::VERSION
8
+ spec.authors = ["Josh"]
9
+
10
+ spec.summary = "Mimics SAML Identity Provider (IdP) responses for testing"
11
+ spec.required_ruby_version = ">= 3.2.0"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.files.delete("Gemfile")
15
+ spec.files.delete(".gitignore")
16
+ spec.files.reject! { |f| f.start_with?("bin/") }
17
+
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "base64"
21
+ spec.add_dependency "ostruct"
22
+ spec.add_dependency "rexml"
23
+
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "rake"
26
+ end
@@ -0,0 +1,329 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class AssertionTest < Minitest::Test
7
+ def setup
8
+ @assertion = Assertion.new.document
9
+ @root = @assertion.root
10
+ @subject = @root.elements["saml:Subject"]
11
+ @sc = @subject.elements["saml:SubjectConfirmation"]
12
+ @scd = @sc.elements["saml:SubjectConfirmationData"]
13
+ @conditions = @root.elements["saml:Conditions"]
14
+ @audience_restriction = @conditions.elements["saml:AudienceRestriction"]
15
+ @authn_statement = @root.elements["saml:AuthnStatement"]
16
+ end
17
+
18
+ def test_root_name
19
+ assert_equal "Assertion", @root.name
20
+ assert_equal "saml", @root.prefix
21
+ end
22
+
23
+ def test_root_namespace
24
+ assert_equal SAML_ASSERTION_NS, @root.namespace
25
+ end
26
+
27
+ def test_root_id
28
+ assert @root.attributes["ID"].start_with?("_")
29
+ end
30
+
31
+ def test_root_version
32
+ assert_equal "2.0", @root.attributes["Version"]
33
+ end
34
+
35
+ def test_root_issue_instant
36
+ instant = Time.iso8601(@root.attributes["IssueInstant"])
37
+ assert_in_delta Time.now.to_i, instant.to_i, 1
38
+ end
39
+
40
+ def test_issuer
41
+ issuer = @root.elements["saml:Issuer"]
42
+ assert_equal "Issuer", issuer.name
43
+ assert_equal "saml", issuer.prefix
44
+ assert_equal DEFAULTS.issuer, issuer.text
45
+ end
46
+
47
+ def test_issuer_override
48
+ assertion = Assertion.new(issuer: "https://test.example.com").document
49
+ issuer = assertion.root.elements["saml:Issuer"]
50
+ assert_equal "https://test.example.com", issuer.text
51
+ end
52
+
53
+ def test_subject
54
+ assert_equal "Subject", @subject.name
55
+ assert_equal "saml", @subject.prefix
56
+ end
57
+
58
+ def test_name_id
59
+ name_id = @subject.elements["saml:NameID"]
60
+ assert_equal "NameID", name_id.name
61
+ assert_equal "saml", name_id.prefix
62
+ assert_equal DEFAULTS.name_id, name_id.text
63
+ end
64
+
65
+ def test_name_id_format_default
66
+ name_id = @subject.elements["saml:NameID"]
67
+ assert_equal NAMEID_EMAIL, name_id.attributes["Format"]
68
+ end
69
+
70
+ def test_name_id_override
71
+ email = "user@test.com"
72
+ refute_equal email, DEFAULTS.name_id
73
+ assertion = Assertion.new(name_id: email).document
74
+ name_id = assertion.root.elements["saml:Subject/saml:NameID"]
75
+ assert_equal email, name_id.text
76
+ end
77
+
78
+ def test_name_id_format_override
79
+ format = NAMEID_PERSISTENT
80
+ refute_equal format, DEFAULTS.name_id_format
81
+ assertion = Assertion.new(name_id_format: format).document
82
+ name_id = assertion.root.elements["saml:Subject/saml:NameID"]
83
+ assert_equal format, name_id.attributes["Format"]
84
+ end
85
+
86
+ def test_subject_confirmation
87
+ assert_equal "SubjectConfirmation", @sc.name
88
+ assert_equal "saml", @sc.prefix
89
+ end
90
+
91
+ def test_subject_confirmation_method
92
+ assert_equal CM_BEARER, @sc.attributes["Method"]
93
+ end
94
+
95
+ def test_subject_confirmation_data
96
+ assert_equal "SubjectConfirmationData", @scd.name
97
+ assert_equal "saml", @scd.prefix
98
+ end
99
+
100
+ def test_valid_for_default
101
+ not_on_or_after = Time.iso8601(@scd.attributes["NotOnOrAfter"])
102
+ expected = Time.now.utc + DEFAULTS.valid_for
103
+ assert_in_delta expected.to_i, not_on_or_after.to_i, 1
104
+ end
105
+
106
+ def test_recipient_default
107
+ assert_equal DEFAULTS.recipient, @scd.attributes["Recipient"]
108
+ end
109
+
110
+ def test_in_response_to_default
111
+ assert_equal DEFAULTS.in_response_to, @scd.attributes["InResponseTo"]
112
+ end
113
+
114
+ def test_recipient_override
115
+ recipient = "https://custom.example.com/acs"
116
+ refute_equal recipient, DEFAULTS.recipient
117
+ assertion = Assertion.new(recipient: recipient).document
118
+ subject = assertion.root.elements["saml:Subject"]
119
+ sc = subject.elements["saml:SubjectConfirmation"]
120
+ scd = sc.elements["saml:SubjectConfirmationData"]
121
+ assert_equal recipient, scd.attributes["Recipient"]
122
+ end
123
+
124
+ def test_in_response_to_override
125
+ in_response_to = "_custom_request"
126
+ refute_equal in_response_to, DEFAULTS.in_response_to
127
+ assertion = Assertion.new(in_response_to: in_response_to).document
128
+ subject = assertion.root.elements["saml:Subject"]
129
+ sc = subject.elements["saml:SubjectConfirmation"]
130
+ scd = sc.elements["saml:SubjectConfirmationData"]
131
+ assert_equal in_response_to, scd.attributes["InResponseTo"]
132
+ end
133
+
134
+ def test_in_response_to_omitted_when_nil
135
+ assertion = Assertion.new(in_response_to: nil).document
136
+ subject = assertion.root.elements["saml:Subject"]
137
+ sc = subject.elements["saml:SubjectConfirmation"]
138
+ scd = sc.elements["saml:SubjectConfirmationData"]
139
+ assert_nil scd.attributes["InResponseTo"]
140
+ end
141
+
142
+ def test_valid_for_override
143
+ valid_for = 600 # 10 minutes
144
+ refute_equal valid_for, DEFAULTS.valid_for
145
+ assertion = Assertion.new(valid_for: valid_for).document
146
+ subject = assertion.root.elements["saml:Subject"]
147
+ sc = subject.elements["saml:SubjectConfirmation"]
148
+ scd = sc.elements["saml:SubjectConfirmationData"]
149
+ not_on_or_after = Time.iso8601(scd.attributes["NotOnOrAfter"])
150
+ expected = Time.now.utc + valid_for
151
+ assert_in_delta expected.to_i, not_on_or_after.to_i, 1
152
+ end
153
+
154
+ def test_conditions
155
+ assert_equal "Conditions", @conditions.name
156
+ assert_equal "saml", @conditions.prefix
157
+ end
158
+
159
+ def test_conditions_not_before
160
+ not_before = Time.iso8601(@conditions.attributes["NotBefore"])
161
+ assert_in_delta Time.now.to_i, not_before.to_i, 1
162
+ end
163
+
164
+ def test_conditions_not_before_override
165
+ not_before = Time.now.utc - 60
166
+ assertion = Assertion.new(not_before: not_before).document
167
+ conditions = assertion.root.elements["saml:Conditions"]
168
+ assert_equal not_before.iso8601, conditions.attributes["NotBefore"]
169
+ end
170
+
171
+ def test_conditions_not_on_or_after
172
+ not_on_or_after = Time.iso8601(@conditions.attributes["NotOnOrAfter"])
173
+ expected = Time.now.utc + DEFAULTS.valid_for
174
+ assert_in_delta expected.to_i, not_on_or_after.to_i, 1
175
+ end
176
+
177
+ def test_conditions_valid_for_override
178
+ valid_for = 600 # 10 minutes
179
+ refute_equal valid_for, DEFAULTS.valid_for
180
+ assertion = Assertion.new(valid_for: valid_for).document
181
+ conditions = assertion.root.elements["saml:Conditions"]
182
+ not_on_or_after = Time.iso8601(conditions.attributes["NotOnOrAfter"])
183
+ expected = Time.now.utc + valid_for
184
+ assert_in_delta expected.to_i, not_on_or_after.to_i, 1
185
+ end
186
+
187
+ def test_audience_restriction
188
+ assert_equal "AudienceRestriction", @audience_restriction.name
189
+ assert_equal "saml", @audience_restriction.prefix
190
+ end
191
+
192
+ def test_audience
193
+ audience = @audience_restriction.elements["saml:Audience"]
194
+ assert_equal "Audience", audience.name
195
+ assert_equal "saml", audience.prefix
196
+ assert_equal DEFAULTS.audience, audience.text
197
+ end
198
+
199
+ def test_audience_override
200
+ audience = "https://custom.sp.example.com"
201
+ refute_equal audience, DEFAULTS.audience
202
+ assertion = Assertion.new(audience: audience).document
203
+ conditions = assertion.root.elements["saml:Conditions"]
204
+ ar = conditions.elements["saml:AudienceRestriction"]
205
+ assert_equal audience, ar.elements["saml:Audience"].text
206
+ end
207
+
208
+ def test_authn_statement
209
+ assert_equal "AuthnStatement", @authn_statement.name
210
+ assert_equal "saml", @authn_statement.prefix
211
+ end
212
+
213
+ def test_authn_statement_authn_instant
214
+ authn_instant = Time.iso8601(@authn_statement.attributes["AuthnInstant"])
215
+ assert_in_delta Time.now.to_i, authn_instant.to_i, 1
216
+ end
217
+
218
+ def test_authn_statement_session_index
219
+ assert @authn_statement.attributes["SessionIndex"].start_with?("_")
220
+ end
221
+
222
+ def test_authn_context
223
+ authn_context = @authn_statement.elements["saml:AuthnContext"]
224
+ assert_equal "AuthnContext", authn_context.name
225
+ assert_equal "saml", authn_context.prefix
226
+ end
227
+
228
+ def test_authn_context_class_ref
229
+ ac = @authn_statement.elements["saml:AuthnContext"]
230
+ class_ref = ac.elements["saml:AuthnContextClassRef"]
231
+ assert_equal "AuthnContextClassRef", class_ref.name
232
+ assert_equal "saml", class_ref.prefix
233
+ assert_equal DEFAULTS.authn_context, class_ref.text
234
+ end
235
+
236
+ def test_authn_context_override
237
+ custom_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
238
+ refute_equal custom_ref, DEFAULTS.authn_context
239
+ assertion = Assertion.new(authn_context: custom_ref).document
240
+ as = assertion.root.elements["saml:AuthnStatement"]
241
+ ac = as.elements["saml:AuthnContext"]
242
+ class_ref = ac.elements["saml:AuthnContextClassRef"]
243
+ assert_equal custom_ref, class_ref.text
244
+ end
245
+
246
+ def test_default_attributes
247
+ as = @root.elements["saml:AttributeStatement"]
248
+ attrs = as.elements.to_a("saml:Attribute")
249
+ assert_equal 2, attrs.size
250
+
251
+ first = attrs.find { |a| a.attributes["Name"] == "first_name" }
252
+ assert_equal "Test", first.elements["saml:AttributeValue"].text
253
+
254
+ last = attrs.find { |a| a.attributes["Name"] == "last_name" }
255
+ assert_equal "User", last.elements["saml:AttributeValue"].text
256
+ end
257
+
258
+ def test_no_attribute_statement_when_empty
259
+ assertion = Assertion.new(attributes: {}).document
260
+ assert_nil assertion.root.elements["saml:AttributeStatement"]
261
+ end
262
+
263
+ def test_attribute_statement_with_single_value
264
+ attributes = { "email" => "user@example.com" }
265
+ assertion = Assertion.new(attributes: attributes).document
266
+ as = assertion.root.elements["saml:AttributeStatement"]
267
+ assert_equal "AttributeStatement", as.name
268
+ assert_equal "saml", as.prefix
269
+ end
270
+
271
+ def test_attribute_name_and_format
272
+ attributes = { "email" => "user@example.com" }
273
+ assertion = Assertion.new(attributes: attributes).document
274
+ attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
275
+ assert_equal "Attribute", attr.name
276
+ assert_equal "saml", attr.prefix
277
+ assert_equal "email", attr.attributes["Name"]
278
+ assert_equal ATTR_NAME_FORMAT, attr.attributes["NameFormat"]
279
+ end
280
+
281
+ def test_attribute_single_value
282
+ attributes = { "email" => "user@example.com" }
283
+ assertion = Assertion.new(attributes: attributes).document
284
+ attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
285
+ value = attr.elements["saml:AttributeValue"]
286
+ assert_equal "AttributeValue", value.name
287
+ assert_equal "saml", value.prefix
288
+ assert_equal "user@example.com", value.text
289
+ end
290
+
291
+ def test_attribute_multi_value
292
+ attributes = { "groups" => ["admin", "users", "developers"] }
293
+ assertion = Assertion.new(attributes: attributes).document
294
+ attr = assertion.root.elements["saml:AttributeStatement/saml:Attribute"]
295
+ values = attr.elements.to_a("saml:AttributeValue")
296
+ assert_equal 3, values.size
297
+ assert_equal "admin", values[0].text
298
+ assert_equal "users", values[1].text
299
+ assert_equal "developers", values[2].text
300
+ end
301
+
302
+ def test_multiple_attributes
303
+ attributes = {
304
+ email: "user@example.com",
305
+ name: "Test User",
306
+ groups: ["admin", "users"]
307
+ }
308
+
309
+ assertion = Assertion.new(attributes: attributes).document
310
+ as = assertion.root.elements["saml:AttributeStatement"]
311
+ attrs = as.elements.to_a("saml:Attribute")
312
+ assert_equal 3, attrs.size
313
+
314
+ email_attr = attrs.find { |a| a.attributes["Name"] == "email" }
315
+ email_value = email_attr.elements["saml:AttributeValue"].text
316
+ assert_equal "user@example.com", email_value
317
+
318
+ name_attr = attrs.find { |a| a.attributes["Name"] == "name" }
319
+ name_value = name_attr.elements["saml:AttributeValue"].text
320
+ assert_equal "Test User", name_value
321
+
322
+ groups_attr = attrs.find { |a| a.attributes["Name"] == "groups" }
323
+ group_values = groups_attr.elements.to_a("saml:AttributeValue")
324
+ assert_equal 2, group_values.size
325
+ assert_equal "admin", group_values[0].text
326
+ assert_equal "users", group_values[1].text
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class CertificateTest < Minitest::Test
7
+ def setup
8
+ @certificate = Certificate.generate
9
+ end
10
+
11
+ def test_private_key_is_rsa
12
+ assert_instance_of OpenSSL::PKey::RSA, @certificate.private_key
13
+ end
14
+
15
+ def test_private_key_defaults_to_2048_bits
16
+ assert_equal 2048, @certificate.private_key.n.num_bits
17
+ end
18
+
19
+ def test_private_key_with_custom_bits
20
+ certificate = Certificate.generate(bits: 4096)
21
+ assert_equal 4096, certificate.private_key.n.num_bits
22
+ end
23
+
24
+ def test_certificate_is_x509
25
+ assert_instance_of OpenSSL::X509::Certificate, @certificate.certificate
26
+ end
27
+
28
+ def test_certificate_not_before_is_now
29
+ assert_in_delta Time.now, @certificate.certificate.not_before, 1
30
+ end
31
+
32
+ def test_certificate_not_after_is_one_year_from_now
33
+ one_year = 365 * 24 * 60 * 60
34
+ assert_in_delta Time.now + one_year, @certificate.certificate.not_after, 1
35
+ end
36
+
37
+ def test_certificate_is_signed
38
+ assert @certificate.certificate.verify(@certificate.private_key)
39
+ end
40
+
41
+ def test_certificate_with_custom_subject
42
+ certificate = Certificate.generate(cn: "Test", o: "Acme")
43
+ assert_equal "/CN=Test/O=Acme", certificate.certificate.subject.to_s
44
+ end
45
+
46
+ def test_certificate_with_custom_valid_for
47
+ certificate = Certificate.generate(valid_for: 30).certificate
48
+ thirty_days = 30 * 24 * 60 * 60
49
+ assert_in_delta Time.now + thirty_days, certificate.not_after, 1
50
+ end
51
+
52
+ def test_certificate_with_valid_until
53
+ valid_until = Time.new(2030, 1, 1)
54
+ certificate = Certificate.generate(valid_until: valid_until)
55
+ assert_equal valid_until, certificate.certificate.not_after
56
+ end
57
+
58
+ def test_private_key_pem
59
+ assert @certificate.private_key_pem.start_with?("-----BEGIN")
60
+ end
61
+
62
+ def test_certificate_pem
63
+ assert @certificate.certificate_pem.start_with?("-----BEGIN")
64
+ end
65
+
66
+ def test_fingerprint
67
+ der = @certificate.certificate.to_der
68
+ expected = OpenSSL::Digest::SHA256.hexdigest(der)
69
+ assert_equal expected, @certificate.fingerprint
70
+ end
71
+
72
+ def test_base64
73
+ der = @certificate.certificate.to_der
74
+ expected = Base64.strict_encode64(der)
75
+ assert_equal expected, @certificate.base64
76
+ end
77
+
78
+ def test_load
79
+ certificate = Certificate.load(
80
+ private_key_pem: @certificate.private_key_pem,
81
+ certificate_pem: @certificate.certificate_pem,
82
+ )
83
+
84
+ assert_equal @certificate.fingerprint, certificate.fingerprint
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class DefaultsTest < Minitest::Test
7
+ def test_issuer
8
+ assert_equal "https://idp.example.com", DEFAULTS.issuer
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class EncryptionTest < Minitest::Test
7
+ def setup
8
+ @assertion = Assertion.new.document
9
+ @element = @assertion.root
10
+ @certificate = Certificate.generate
11
+ @encrypted = Encryption.new(@element, @certificate).encrypt
12
+ @ed = @encrypted.elements["xenc:EncryptedData"]
13
+ @ki = @ed.elements["ds:KeyInfo"]
14
+ @ek = @ki.elements["xenc:EncryptedKey"]
15
+ @cd = @ed.elements["xenc:CipherData"]
16
+ end
17
+
18
+ def test_returns_encrypted_assertion_element
19
+ assert_equal "EncryptedAssertion", @encrypted.name
20
+ assert_equal "saml", @encrypted.prefix
21
+ end
22
+
23
+ def test_encrypted_assertion_namespace
24
+ assert_equal SAML_ASSERTION_NS, @encrypted.namespace
25
+ end
26
+
27
+ def test_encrypted_data_element
28
+ assert_equal "EncryptedData", @ed.name
29
+ assert_equal "xenc", @ed.prefix
30
+ end
31
+
32
+ def test_encrypted_data_namespace
33
+ assert_equal XMLENC_NS, @ed.namespace
34
+ end
35
+
36
+ def test_encrypted_data_type
37
+ assert_equal "#{XMLENC_NS}Element", @ed.attributes["Type"]
38
+ end
39
+
40
+ def test_encryption_method_element
41
+ em = @ed.elements["xenc:EncryptionMethod"]
42
+ assert_equal "EncryptionMethod", em.name
43
+ assert_equal "xenc", em.prefix
44
+ end
45
+
46
+ def test_encryption_method_algorithm
47
+ em = @ed.elements["xenc:EncryptionMethod"]
48
+ assert_equal AES256_CBC, em.attributes["Algorithm"]
49
+ end
50
+
51
+ def test_cipher_data_element
52
+ assert_equal "CipherData", @cd.name
53
+ assert_equal "xenc", @cd.prefix
54
+ end
55
+
56
+ def test_cipher_value_element
57
+ cv = @cd.elements["xenc:CipherValue"]
58
+ assert_equal "CipherValue", cv.name
59
+ assert_equal "xenc", cv.prefix
60
+ end
61
+
62
+ def test_cipher_value_is_base64
63
+ cv = @cd.elements["xenc:CipherValue"]
64
+ decoded = Base64.strict_decode64(cv.text)
65
+ assert decoded.bytesize > 16
66
+ end
67
+
68
+ def test_cipher_value_starts_with_iv
69
+ cv = @cd.elements["xenc:CipherValue"]
70
+ decoded = Base64.strict_decode64(cv.text)
71
+ iv = decoded[0, 16]
72
+ assert_equal 16, iv.bytesize
73
+ end
74
+
75
+ def test_key_info_element
76
+ assert_equal "KeyInfo", @ki.name
77
+ assert_equal "ds", @ki.prefix
78
+ end
79
+
80
+ def test_key_info_namespace
81
+ assert_equal XMLDSIG_NS, @ki.namespace
82
+ end
83
+
84
+ def test_encrypted_key_element
85
+ assert_equal "EncryptedKey", @ek.name
86
+ assert_equal "xenc", @ek.prefix
87
+ end
88
+
89
+ def test_encrypted_key_namespace
90
+ assert_equal XMLENC_NS, @ek.namespace
91
+ end
92
+
93
+ def test_encrypted_key_encryption_method
94
+ em = @ek.elements["xenc:EncryptionMethod"]
95
+ assert_equal "EncryptionMethod", em.name
96
+ assert_equal RSA_OAEP, em.attributes["Algorithm"]
97
+ end
98
+
99
+ def test_encrypted_key_cipher_data
100
+ cd = @ek.elements["xenc:CipherData"]
101
+ assert_equal "CipherData", cd.name
102
+ assert_equal "xenc", cd.prefix
103
+ end
104
+
105
+ def test_encrypted_key_cipher_value
106
+ cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
107
+ assert_equal "CipherValue", cv.name
108
+ assert_equal "xenc", cv.prefix
109
+ end
110
+
111
+ def test_encrypted_key_cipher_value_is_base64
112
+ cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
113
+ decoded = Base64.strict_decode64(cv.text)
114
+ assert decoded.bytesize > 0
115
+ end
116
+
117
+ def test_encrypted_key_can_be_decrypted
118
+ cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
119
+ encrypted_key = Base64.strict_decode64(cv.text)
120
+ private_key = @certificate.private_key
121
+ padding = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
122
+ decrypted_key = private_key.private_decrypt(encrypted_key, padding)
123
+ assert_equal 32, decrypted_key.bytesize
124
+ end
125
+ end
126
+ end