saml-kit 0.1.0 → 0.2.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/exe/saml-kit-decode-http-post +8 -0
  3. data/lib/saml/kit.rb +4 -4
  4. data/lib/saml/kit/authentication_request.rb +3 -13
  5. data/lib/saml/kit/bindings.rb +45 -0
  6. data/lib/saml/kit/bindings/binding.rb +42 -0
  7. data/lib/saml/kit/bindings/http_post.rb +29 -0
  8. data/lib/saml/kit/bindings/http_redirect.rb +61 -0
  9. data/lib/saml/kit/bindings/url_builder.rb +40 -0
  10. data/lib/saml/kit/configuration.rb +22 -1
  11. data/lib/saml/kit/crypto.rb +16 -0
  12. data/lib/saml/kit/crypto/oaep_cipher.rb +22 -0
  13. data/lib/saml/kit/crypto/rsa_cipher.rb +23 -0
  14. data/lib/saml/kit/crypto/simple_cipher.rb +38 -0
  15. data/lib/saml/kit/crypto/unknown_cipher.rb +18 -0
  16. data/lib/saml/kit/cryptography.rb +30 -0
  17. data/lib/saml/kit/default_registry.rb +1 -0
  18. data/lib/saml/kit/document.rb +6 -2
  19. data/lib/saml/kit/identity_provider_metadata.rb +9 -9
  20. data/lib/saml/kit/locales/en.yml +4 -3
  21. data/lib/saml/kit/logout_request.rb +2 -2
  22. data/lib/saml/kit/logout_response.rb +3 -3
  23. data/lib/saml/kit/metadata.rb +12 -37
  24. data/lib/saml/kit/namespaces.rb +1 -13
  25. data/lib/saml/kit/respondable.rb +4 -0
  26. data/lib/saml/kit/response.rb +120 -37
  27. data/lib/saml/kit/service_provider_metadata.rb +16 -7
  28. data/lib/saml/kit/signature.rb +16 -13
  29. data/lib/saml/kit/trustable.rb +14 -6
  30. data/lib/saml/kit/version.rb +1 -1
  31. data/lib/saml/kit/xml.rb +19 -3
  32. data/saml-kit.gemspec +2 -2
  33. metadata +23 -14
  34. data/lib/saml/kit/binding.rb +0 -40
  35. data/lib/saml/kit/http_post_binding.rb +0 -27
  36. data/lib/saml/kit/http_redirect_binding.rb +0 -58
  37. data/lib/saml/kit/url_builder.rb +0 -38
@@ -0,0 +1,38 @@
1
+ module Saml
2
+ module Kit
3
+ module Crypto
4
+ class SimpleCipher
5
+ ALGORITHMS = {
6
+ 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => 'DES-EDE3-CBC',
7
+ 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => 'AES-128-CBC',
8
+ 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => 'AES-192-CBC',
9
+ 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => 'AES-256-CBC',
10
+ }
11
+
12
+ def initialize(algorithm, private_key)
13
+ @algorithm = algorithm
14
+ @private_key = private_key
15
+ end
16
+
17
+ def self.matches?(algorithm)
18
+ ALGORITHMS[algorithm]
19
+ end
20
+
21
+ def decrypt(cipher_text)
22
+ cipher = OpenSSL::Cipher.new(ALGORITHMS[@algorithm])
23
+ cipher.decrypt
24
+ iv = cipher_text[0..cipher.iv_len-1]
25
+ data = cipher_text[cipher.iv_len..-1]
26
+ #cipher.padding = 0
27
+ cipher.key = @private_key
28
+ cipher.iv = iv
29
+
30
+ Saml::Kit.logger.debug ['-key', @private_key].inspect
31
+ Saml::Kit.logger.debug ['-iv', iv].inspect
32
+
33
+ cipher.update(data) + cipher.final
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ module Saml
2
+ module Kit
3
+ module Crypto
4
+ class UnknownCipher
5
+ def initialize(algorithm, key)
6
+ end
7
+
8
+ def self.matches?(algorithm)
9
+ true
10
+ end
11
+
12
+ def decrypt(cipher_text)
13
+ cipher_text
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Saml
2
+ module Kit
3
+ class Cryptography
4
+ attr_reader :private_key
5
+
6
+ def initialize(private_key = Saml::Kit.configuration.encryption_private_key)
7
+ @private_key = private_key
8
+ end
9
+
10
+ def decrypt(data)
11
+ encrypt_data = data['EncryptedData']
12
+ symmetric_key = symmetric_key_from(encrypt_data)
13
+ cipher_text = Base64.decode64(encrypt_data["CipherData"]["CipherValue"])
14
+ to_plaintext(cipher_text, symmetric_key, encrypt_data["EncryptionMethod"]['Algorithm'])
15
+ end
16
+
17
+ private
18
+
19
+ def symmetric_key_from(encrypted_data)
20
+ encrypted_key = encrypted_data['KeyInfo']['EncryptedKey']
21
+ cipher_text = Base64.decode64(encrypted_key['CipherData']['CipherValue'])
22
+ to_plaintext(cipher_text, private_key, encrypted_key["EncryptionMethod"]['Algorithm'])
23
+ end
24
+
25
+ def to_plaintext(cipher_text, symmetric_key, algorithm)
26
+ return Crypto.decryptor_for(algorithm, symmetric_key).decrypt(cipher_text)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -6,6 +6,7 @@ module Saml
6
6
  end
7
7
 
8
8
  def register(metadata)
9
+ Saml::Kit.logger.debug(metadata.to_xml(pretty: true))
9
10
  @items[metadata.entity_id] = metadata
10
11
  end
11
12
 
@@ -48,8 +48,12 @@ module Saml
48
48
  @xml_hash
49
49
  end
50
50
 
51
- def to_xml
52
- content
51
+ def to_xml(pretty: false)
52
+ pretty ? Nokogiri::XML(content).to_xml(indent: 2) : content
53
+ end
54
+
55
+ def to_xhtml
56
+ Nokogiri::XML(content, &:noblanks).to_xhtml
53
57
  end
54
58
 
55
59
  def to_s
@@ -7,7 +7,7 @@ module Saml
7
7
 
8
8
  def want_authn_requests_signed
9
9
  xpath = "/md:EntityDescriptor/md:#{name}"
10
- attribute = find_by(xpath).attribute("WantAuthnRequestsSigned")
10
+ attribute = document.find_by(xpath).attribute("WantAuthnRequestsSigned")
11
11
  return true if attribute.nil?
12
12
  attribute.text.downcase == "true"
13
13
  end
@@ -21,7 +21,7 @@ module Saml
21
21
  end
22
22
 
23
23
  def attributes
24
- find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
24
+ document.find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
25
25
  {
26
26
  format: item.attribute("NameFormat").try(:value),
27
27
  name: item.attribute("Name").value,
@@ -48,19 +48,19 @@ module Saml
48
48
  @want_authn_requests_signed = true
49
49
  end
50
50
 
51
- def add_single_sign_on_service(url, binding: :post)
52
- @single_sign_on_urls.push(location: url, binding: Namespaces.binding_for(binding))
51
+ def add_single_sign_on_service(url, binding: :http_post)
52
+ @single_sign_on_urls.push(location: url, binding: Bindings.binding_for(binding))
53
53
  end
54
54
 
55
- def add_single_logout_service(url, binding: :post)
56
- @logout_urls.push(location: url, binding: Namespaces.binding_for(binding))
55
+ def add_single_logout_service(url, binding: :http_post)
56
+ @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
57
57
  end
58
58
 
59
59
  def to_xml
60
- Signature.sign(id, sign: sign) do |xml, signature|
60
+ Signature.sign(sign: sign) do |xml, signature|
61
61
  xml.instruct!
62
62
  xml.EntityDescriptor entity_descriptor_options do
63
- signature.template(xml)
63
+ signature.template(id)
64
64
  xml.IDPSSODescriptor idp_sso_descriptor_options do
65
65
  xml.KeyDescriptor use: "signing" do
66
66
  xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
@@ -112,8 +112,8 @@ module Saml
112
112
 
113
113
  def idp_sso_descriptor_options
114
114
  {
115
+ WantAuthnRequestsSigned: want_authn_requests_signed,
115
116
  protocolSupportEnumeration: Namespaces::PROTOCOL,
116
- WantAuthnRequestsSigned: want_authn_requests_signed
117
117
  }
118
118
  end
119
119
  end
@@ -14,12 +14,13 @@ en:
14
14
  LogoutResponse:
15
15
  unregistered: "is unregistered."
16
16
  Response:
17
- invalid: "must contain Response."
18
- unregistered: "must originate from registered identity provider."
19
17
  expired: "must not be expired."
20
- invalid_version: "must be 2.0."
18
+ invalid: "must contain Response."
19
+ invalid_fingerprint: "does not match."
21
20
  invalid_response_to: "must match request id."
21
+ invalid_version: "must be 2.0."
22
22
  must_match_issuer: "must match entityId."
23
+ unregistered: "must originate from registered identity provider."
23
24
  SPSSODescriptor:
24
25
  invalid: "must contain SPSSODescriptor."
25
26
  invalid_signature: "invalid signature."
@@ -40,11 +40,11 @@ module Saml
40
40
  end
41
41
 
42
42
  def to_xml
43
- Signature.sign(id, sign: sign) do |xml, signature|
43
+ Signature.sign(sign: sign) do |xml, signature|
44
44
  xml.instruct!
45
45
  xml.LogoutRequest logout_request_options do
46
46
  xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
47
- signature.template(xml)
47
+ signature.template(id)
48
48
  xml.NameID name_id_options, user.name_id_for(name_id_format)
49
49
  end
50
50
  end
@@ -25,15 +25,15 @@ module Saml
25
25
  @issuer = configuration.issuer
26
26
  provider = configuration.registry.metadata_for(@issuer)
27
27
  if provider
28
- @destination = provider.single_logout_service_for(binding: :post).try(:location)
28
+ @destination = provider.single_logout_service_for(binding: :http_post).try(:location)
29
29
  end
30
30
  end
31
31
 
32
32
  def to_xml
33
- Signature.sign(id, sign: sign) do |xml, signature|
33
+ Signature.sign(sign: sign) do |xml, signature|
34
34
  xml.LogoutResponse logout_response_options do
35
35
  xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
36
- signature.template(xml)
36
+ signature.template(id)
37
37
  xml.Status do
38
38
  xml.StatusCode Value: status_code
39
39
  end
@@ -3,14 +3,7 @@ module Saml
3
3
  class Metadata
4
4
  include ActiveModel::Validations
5
5
  include XsdValidatable
6
-
7
6
  METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
8
- NAMESPACES = {
9
- "NameFormat": Namespaces::ATTR_SPLAT,
10
- "ds": Namespaces::XMLDSIG,
11
- "md": Namespaces::METADATA,
12
- "saml": Namespaces::ASSERTION,
13
- }.freeze
14
7
 
15
8
  validates_presence_of :metadata
16
9
  validate :must_contain_descriptor
@@ -27,16 +20,16 @@ module Saml
27
20
  end
28
21
 
29
22
  def entity_id
30
- find_by("/md:EntityDescriptor/@entityID").value
23
+ document.find_by("/md:EntityDescriptor/@entityID").value
31
24
  end
32
25
 
33
26
  def name_id_formats
34
- find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
27
+ document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
35
28
  end
36
29
 
37
30
  def certificates
38
- @certificates ||= find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
39
- cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", NAMESPACES).text
31
+ @certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
32
+ cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", Xml::NAMESPACES).text
40
33
  {
41
34
  text: cert,
42
35
  fingerprint: Fingerprint.new(cert).algorithm(hash_algorithm),
@@ -54,15 +47,15 @@ module Saml
54
47
  end
55
48
 
56
49
  def services(type)
57
- find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
50
+ document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
58
51
  binding = item.attribute("Binding").value
59
52
  location = item.attribute("Location").value
60
- binding_for(binding, location)
53
+ Saml::Kit::Bindings.create_for(binding, location)
61
54
  end
62
55
  end
63
56
 
64
57
  def service_for(binding:, type:)
65
- binding = Saml::Kit::Namespaces.binding_for(binding)
58
+ binding = Saml::Kit::Bindings.binding_for(binding)
66
59
  services(type).find { |x| x.binding?(binding) }
67
60
  end
68
61
 
@@ -78,6 +71,7 @@ module Saml
78
71
  if :signing == use.to_sym
79
72
  hash_value = fingerprint.algorithm(hash_algorithm)
80
73
  signing_certificates.find do |signing_certificate|
74
+ Saml::Kit.logger.debug [hash_value, signing_certificate[:fingerprint]].inspect
81
75
  hash_value == signing_certificate[:fingerprint]
82
76
  end
83
77
  end
@@ -87,8 +81,8 @@ module Saml
87
81
  @xml_hash ||= Hash.from_xml(to_xml)
88
82
  end
89
83
 
90
- def to_xml
91
- @xml
84
+ def to_xml(pretty: false)
85
+ document.to_xml(pretty: pretty)
92
86
  end
93
87
 
94
88
  def to_s
@@ -116,19 +110,11 @@ module Saml
116
110
  private
117
111
 
118
112
  def document
119
- @document ||= Nokogiri::XML(@xml)
120
- end
121
-
122
- def find_by(xpath)
123
- document.at_xpath(xpath, NAMESPACES)
124
- end
125
-
126
- def find_all(xpath)
127
- document.search(xpath, NAMESPACES)
113
+ @document ||= Xml.new(xml)
128
114
  end
129
115
 
130
116
  def metadata
131
- find_by("/md:EntityDescriptor/md:#{name}").present?
117
+ document.find_by("/md:EntityDescriptor/md:#{name}").present?
132
118
  end
133
119
 
134
120
  def must_contain_descriptor
@@ -155,17 +141,6 @@ module Saml
155
141
  end
156
142
  result
157
143
  end
158
-
159
- def binding_for(binding, location)
160
- case binding
161
- when Namespaces::HTTP_REDIRECT
162
- Saml::Kit::HttpRedirectBinding.new(location: location)
163
- when Namespaces::POST
164
- Saml::Kit::HttpPostBinding.new(location: location)
165
- else
166
- Saml::Kit::Binding.new(binding: binding, location: location)
167
- end
168
- end
169
144
  end
170
145
  end
171
146
  end
@@ -7,9 +7,6 @@ module Saml
7
7
  BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
8
8
  EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
9
9
  ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
10
- HTTP_ARTIFACT = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'
11
- HTTP_POST = POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
12
- HTTP_REDIRECT = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
13
10
  METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
14
11
  PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
15
12
  PASSWORD_PROTECTED = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
@@ -32,16 +29,7 @@ module Saml
32
29
  URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
33
30
  VERSION_MISMATCH_ERROR = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"
34
31
  XMLDSIG = "http://www.w3.org/2000/09/xmldsig#"
35
-
36
- def self.binding_for(binding)
37
- if :post == binding
38
- Namespaces::HTTP_POST
39
- elsif :http_redirect == binding
40
- Namespaces::HTTP_REDIRECT
41
- else
42
- nil
43
- end
44
- end
32
+ XMLENC = "http://www.w3.org/2001/04/xmlenc#"
45
33
  end
46
34
  end
47
35
  end
@@ -21,6 +21,10 @@ module Saml
21
21
  to_h.fetch(name, {}).fetch('InResponseTo', nil)
22
22
  end
23
23
 
24
+ def success?
25
+ Namespaces::SUCCESS == status_code
26
+ end
27
+
24
28
  private
25
29
 
26
30
  def must_match_request_id
@@ -12,7 +12,7 @@ module Saml
12
12
  end
13
13
 
14
14
  def name_id
15
- to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Subject', {}).fetch('NameID', nil)
15
+ assertion.fetch('Subject', {}).fetch('NameID', nil)
16
16
  end
17
17
 
18
18
  def [](key)
@@ -20,18 +20,19 @@ module Saml
20
20
  end
21
21
 
22
22
  def attributes
23
- @attributes ||= Hash[to_h.fetch(name, {}).fetch('Assertion', {}).fetch('AttributeStatement', {}).fetch('Attribute', []).map do |item|
24
- [item['Name'].to_sym, item['AttributeValue']]
25
- end].with_indifferent_access
23
+ @attributes ||= Hash[
24
+ assertion.fetch('AttributeStatement', {}).fetch('Attribute', []).map do |item|
25
+ [item['Name'].to_sym, item['AttributeValue']]
26
+ end
27
+ ].with_indifferent_access
26
28
  end
27
29
 
28
-
29
30
  def started_at
30
- parse_date(to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Conditions', {}).fetch('NotBefore', nil))
31
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
31
32
  end
32
33
 
33
34
  def expired_at
34
- parse_date(to_h.fetch(name, {}).fetch('Assertion', {}).fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
35
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
35
36
  end
36
37
 
37
38
  def expired?
@@ -42,15 +43,42 @@ module Saml
42
43
  Time.current > started_at && !expired?
43
44
  end
44
45
 
46
+ def encrypted?
47
+ to_h[name]['EncryptedAssertion'].present?
48
+ end
49
+
50
+ def assertion
51
+ @assertion =
52
+ begin
53
+ if encrypted?
54
+ decrypted = Cryptography.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
55
+ Saml::Kit.logger.debug(decrypted)
56
+ Hash.from_xml(decrypted)['Assertion']
57
+ else
58
+ to_h.fetch(name, {}).fetch('Assertion', {})
59
+ end
60
+ end
61
+ end
62
+
63
+ def signed?
64
+ super || assertion.fetch('Signature', nil).present?
65
+ end
66
+
67
+ def certificate
68
+ super || assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
69
+ end
70
+
45
71
  private
46
72
 
47
73
  def must_be_active_session
48
74
  return unless expected_type?
75
+ return unless success?
49
76
  errors[:base] << error_message(:expired) unless active?
50
77
  end
51
78
 
52
79
  def must_match_issuer
53
80
  return unless expected_type?
81
+ return unless success?
54
82
 
55
83
  unless audiences.include?(Saml::Kit.configuration.issuer)
56
84
  errors[:audience] << error_message(:must_match_issuer)
@@ -58,7 +86,7 @@ module Saml
58
86
  end
59
87
 
60
88
  def audiences
61
- Array(to_h[name]['Assertion']['Conditions']['AudienceRestriction']['Audience'])
89
+ Array(assertion['Conditions']['AudienceRestriction']['Audience'])
62
90
  rescue => error
63
91
  Saml::Kit.logger.error(error)
64
92
  []
@@ -75,7 +103,7 @@ module Saml
75
103
  attr_reader :user, :request
76
104
  attr_accessor :id, :reference_id, :now
77
105
  attr_accessor :version, :status_code
78
- attr_accessor :issuer, :sign, :destination
106
+ attr_accessor :issuer, :sign, :destination, :encrypt
79
107
 
80
108
  def initialize(user, request)
81
109
  @user = user
@@ -86,8 +114,9 @@ module Saml
86
114
  @version = "2.0"
87
115
  @status_code = Namespaces::SUCCESS
88
116
  @issuer = configuration.issuer
89
- @destination = request.acs_url
117
+ @destination = destination_for(request)
90
118
  @sign = want_assertions_signed
119
+ @encrypt = false
91
120
  end
92
121
 
93
122
  def want_assertions_signed
@@ -98,38 +127,51 @@ module Saml
98
127
  end
99
128
 
100
129
  def to_xml
101
- Signature.sign(id, sign: sign) do |xml, signature|
130
+ Signature.sign(sign: sign) do |xml, signature|
102
131
  xml.Response response_options do
103
132
  xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
104
- signature.template(xml)
133
+ signature.template(id)
105
134
  xml.Status do
106
135
  xml.StatusCode Value: status_code
107
136
  end
108
- xml.Assertion(assertion_options) do
109
- xml.Issuer issuer
110
- xml.Subject do
111
- xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
112
- xml.SubjectConfirmation Method: Namespaces::BEARER do
113
- xml.SubjectConfirmationData "", subject_confirmation_data_options
114
- end
137
+ assertion(xml, signature)
138
+ end
139
+ end
140
+ end
141
+
142
+ def build
143
+ Response.new(to_xml, request_id: request.id)
144
+ end
145
+
146
+ private
147
+
148
+ def assertion(xml, signature)
149
+ with_encryption(xml) do |xml|
150
+ xml.Assertion(assertion_options) do
151
+ xml.Issuer issuer
152
+ signature.template(reference_id) unless encrypt
153
+ xml.Subject do
154
+ xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
155
+ xml.SubjectConfirmation Method: Namespaces::BEARER do
156
+ xml.SubjectConfirmationData "", subject_confirmation_data_options
115
157
  end
116
- xml.Conditions conditions_options do
117
- xml.AudienceRestriction do
118
- xml.Audience request.issuer
119
- end
158
+ end
159
+ xml.Conditions conditions_options do
160
+ xml.AudienceRestriction do
161
+ xml.Audience request.issuer
120
162
  end
121
- xml.AuthnStatement authn_statement_options do
122
- xml.AuthnContext do
123
- xml.AuthnContextClassRef Namespaces::PASSWORD
124
- end
163
+ end
164
+ xml.AuthnStatement authn_statement_options do
165
+ xml.AuthnContext do
166
+ xml.AuthnContextClassRef Namespaces::PASSWORD
125
167
  end
126
- assertion_attributes = user.assertion_attributes_for(request)
127
- if assertion_attributes.any?
128
- xml.AttributeStatement do
129
- assertion_attributes.each do |key, value|
130
- xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
131
- xml.AttributeValue value.to_s
132
- end
168
+ end
169
+ assertion_attributes = user.assertion_attributes_for(request)
170
+ if assertion_attributes.any?
171
+ xml.AttributeStatement do
172
+ assertion_attributes.each do |key, value|
173
+ xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
174
+ xml.AttributeValue value.to_s
133
175
  end
134
176
  end
135
177
  end
@@ -138,11 +180,52 @@ module Saml
138
180
  end
139
181
  end
140
182
 
141
- def build
142
- Response.new(to_xml, request_id: request.id)
183
+ def with_encryption(xml)
184
+ if encrypt
185
+ temp = ::Builder::XmlMarkup.new
186
+ yield temp
187
+ raw_xml_to_encrypt = temp.target!
188
+
189
+ encryption_certificate = OpenSSL::X509::Certificate.new(Base64.decode64(request.provider.encryption_certificates.first[:text]))
190
+ public_key = encryption_certificate.public_key
191
+
192
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
193
+ cipher.encrypt
194
+ key = cipher.random_key
195
+ iv = cipher.random_iv
196
+ encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
197
+
198
+ Saml::Kit.logger.debug ['+iv', iv].inspect
199
+ Saml::Kit.logger.debug ['+key', key].inspect
200
+
201
+ xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
202
+ xml.EncryptedData xmlns: Namespaces::XMLENC do
203
+ xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
204
+ xml.KeyInfo xmlns: Namespaces::XMLDSIG do
205
+ xml.EncryptedKey xmlns: Namespaces::XMLENC do
206
+ xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
207
+ xml.CipherData do
208
+ xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
209
+ end
210
+ end
211
+ end
212
+ xml.CipherData do
213
+ xml.CipherValue Base64.encode64(iv + encrypted)
214
+ end
215
+ end
216
+ end
217
+ else
218
+ yield xml
219
+ end
143
220
  end
144
221
 
145
- private
222
+ def destination_for(request)
223
+ if request.signed? && request.trusted?
224
+ request.acs_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
225
+ else
226
+ request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
227
+ end
228
+ end
146
229
 
147
230
  def configuration
148
231
  Saml::Kit.configuration