ruby-saml-mod 0.1.30 → 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.
- checksums.yaml +4 -4
- data/lib/onelogin/saml.rb +3 -2
- data/lib/onelogin/saml/auth_request.rb +37 -43
- data/lib/onelogin/saml/base_assertion.rb +159 -0
- data/lib/onelogin/saml/logout_request.rb +44 -0
- data/lib/onelogin/saml/logout_response.rb +24 -25
- data/lib/onelogin/saml/response.rb +2 -4
- data/lib/onelogin/saml/settings.rb +23 -17
- data/spec/base_assertion_spec.rb +5 -0
- data/spec/fixtures/logout_request.xml +11 -0
- data/spec/fixtures/logout_response.xml +13 -0
- data/spec/fixtures/test1-cert.pem +21 -0
- data/spec/fixtures/test1-key.pem +15 -0
- data/spec/fixtures/test1-response.xml +62 -0
- data/spec/fixtures/test2-response.xml +70 -0
- data/spec/fixtures/test3-response.xml +9 -0
- data/spec/fixtures/test4-response.xml +57 -0
- data/spec/fixtures/test5-response.xml +48 -0
- data/spec/fixtures/test6-response.xml +9 -0
- data/spec/fixtures/wrong-key.pem +15 -0
- data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +11 -0
- data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +45 -0
- data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +44 -0
- data/spec/logout_request_spec.rb +89 -0
- data/spec/logout_response_spec.rb +76 -0
- data/spec/meta_data_spec.rb +39 -0
- data/spec/response_spec.rb +193 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/test_server.rb +73 -0
- metadata +77 -10
- data/LICENSE +0 -19
- data/README +0 -7
- data/lib/onelogin/saml/log_out_request.rb +0 -54
- data/ruby-saml-mod.gemspec +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9b8339af98b853334c4cd343eff1454a9ed537a
|
4
|
+
data.tar.gz: 7702ec985b556013a1d812f646e415ec16cfe58f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e1accc268ecc2fad5023f99552d8c2a53cca7ddd548092236534bcb0d6016ec51a40d0960b8706afceacd9fe5ecfeb204751985f0b15c90e78e1bbaeeb5cade
|
7
|
+
data.tar.gz: 868e76914f77d9766ebc31449b9262c28dd689a4ea815ed23c6269ada3a0e4422ec2941e73c87bfd3e9d53671ee0e5c0c1f6b58ab2151ba969cee116a3599f7d
|
data/lib/onelogin/saml.rb
CHANGED
@@ -35,12 +35,13 @@ module Onelogin
|
|
35
35
|
}
|
36
36
|
end
|
37
37
|
|
38
|
+
require 'onelogin/saml/base_assertion'
|
38
39
|
require 'onelogin/saml/auth_request'
|
39
|
-
require 'onelogin/saml/authn_contexts
|
40
|
+
require 'onelogin/saml/authn_contexts'
|
40
41
|
require 'onelogin/saml/response'
|
41
42
|
require 'onelogin/saml/settings'
|
42
43
|
require 'onelogin/saml/name_identifiers'
|
43
44
|
require 'onelogin/saml/status_codes'
|
44
45
|
require 'onelogin/saml/meta_data'
|
45
|
-
require 'onelogin/saml/
|
46
|
+
require 'onelogin/saml/logout_request'
|
46
47
|
require 'onelogin/saml/logout_response'
|
@@ -1,53 +1,47 @@
|
|
1
1
|
module Onelogin::Saml
|
2
|
-
class AuthRequest
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
class AuthRequest < BaseAssertion
|
3
|
+
attr_accessor :requested_authn_context,
|
4
|
+
:assertion_consumer_service_url,
|
5
|
+
:name_identifier_format
|
6
|
+
|
7
|
+
def self.parse(raw_assertion, settings = nil)
|
8
|
+
raise NotImplementedError
|
8
9
|
end
|
9
|
-
|
10
|
-
def self.
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
def self.generate(settings)
|
12
|
+
super(settings, {
|
13
|
+
destination: settings.idp_sso_target_url,
|
14
|
+
requested_authn_context: settings.requested_authn_context,
|
15
|
+
assertion_consumer_service_url: Array(settings.assertion_consumer_service_url).first,
|
16
|
+
name_identifier_format: settings.name_identifier_format
|
17
|
+
})
|
13
18
|
end
|
14
|
-
|
15
|
-
def generate_request
|
16
|
-
@id = Onelogin::Saml::AuthRequest.generate_unique_id(42)
|
17
|
-
issue_instant = Onelogin::Saml::AuthRequest.get_timestamp
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@request_xml += "<saml:AuthnContextClassRef xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{@settings.requested_authn_context}</saml:AuthnContextClassRef>"
|
27
|
-
@request_xml += "</samlp:RequestedAuthnContext>\n"
|
20
|
+
def generate
|
21
|
+
if self.requested_authn_context
|
22
|
+
xml = <<-XML
|
23
|
+
<samlp:RequestedAuthnContext Comparison="exact">
|
24
|
+
<saml:AuthnContextClassRef>#{self.requested_authn_context}</saml:AuthnContextClassRef>
|
25
|
+
</samlp:RequestedAuthnContext>
|
26
|
+
XML
|
28
27
|
end
|
29
|
-
|
30
|
-
@request_xml += "</samlp:AuthnRequest>"
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
<<-XML
|
30
|
+
<samlp:AuthnRequest
|
31
|
+
xmlns:samlp="#{Onelogin::NAMESPACES['samlp']}"
|
32
|
+
xmlns:saml="#{Onelogin::NAMESPACES['saml']}"
|
33
|
+
ID="#{self.id}"
|
34
|
+
Version="2.0"
|
35
|
+
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
36
|
+
AssertionConsumerServiceURL=\"#{self.assertion_consumer_service_url}\"
|
37
|
+
IssueInstant="#{self.issue_instant}">
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
chars = ("a".."f").to_a + ("0".."9").to_a
|
43
|
-
chars_len = chars.size
|
44
|
-
unique_id = ("a".."f").to_a[rand(6)]
|
45
|
-
2.upto(length) { |i| unique_id << chars[rand(chars_len)] }
|
46
|
-
unique_id
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.get_timestamp
|
50
|
-
Time.new.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
39
|
+
<saml:Issuer>#{self.issuer}</saml:Issuer>
|
40
|
+
<samlp:NameIDPolicy Format="#{self.name_identifier_format}" AllowCreate="true"></samlp:NameIDPolicy>
|
41
|
+
|
42
|
+
#{xml}
|
43
|
+
</samlp:AuthnRequest>
|
44
|
+
XML
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Onelogin::Saml
|
2
|
+
class BaseAssertion
|
3
|
+
attr_accessor :settings
|
4
|
+
attr_reader :xml
|
5
|
+
attr_writer :id,
|
6
|
+
:issuer,
|
7
|
+
:issue_instant,
|
8
|
+
:destination,
|
9
|
+
:in_response_to,
|
10
|
+
:base64_assertion
|
11
|
+
def id
|
12
|
+
@id ||= root_attribute_value('ID')
|
13
|
+
end
|
14
|
+
|
15
|
+
def issue_instant
|
16
|
+
@issue_instant ||= root_attribute_value('IssueInstance')
|
17
|
+
end
|
18
|
+
|
19
|
+
def issuer
|
20
|
+
@issuer ||= node_content('saml:Issuer')
|
21
|
+
end
|
22
|
+
|
23
|
+
def destination
|
24
|
+
@destination ||= root_attribute_value('Destination')
|
25
|
+
end
|
26
|
+
|
27
|
+
def in_response_to
|
28
|
+
@in_response_to ||= root_attribute_value('InResponseTo')
|
29
|
+
end
|
30
|
+
|
31
|
+
def document
|
32
|
+
@document ||= LibXML::XML::Document.string(xml) if xml
|
33
|
+
end
|
34
|
+
|
35
|
+
def xml=(value)
|
36
|
+
@xml = value.strip
|
37
|
+
end
|
38
|
+
|
39
|
+
def base64_assertion
|
40
|
+
@base64_assertion ||= begin
|
41
|
+
deflated_assertion = Zlib::Deflate.deflate(self.xml, 9)[2..-5]
|
42
|
+
Base64.strict_encode64(deflated_assertion)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def assertion_type
|
47
|
+
return unless document
|
48
|
+
|
49
|
+
if document.root.name =~ /Request$/
|
50
|
+
:request
|
51
|
+
elsif document.root.name =~ /Response$/
|
52
|
+
:response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def process(settings)
|
57
|
+
# TODO: Verify signature and decrypt.
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.parse(raw_assertion, settings = nil)
|
61
|
+
assertion = new
|
62
|
+
assertion.base64_assertion = raw_assertion
|
63
|
+
|
64
|
+
decoded_xml = Base64.decode64(raw_assertion)
|
65
|
+
zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
66
|
+
|
67
|
+
assertion.xml = zlib.inflate(decoded_xml)
|
68
|
+
|
69
|
+
assertion.process(settings) if settings
|
70
|
+
assertion
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.generate(settings, attributes = {})
|
74
|
+
assertion = new
|
75
|
+
|
76
|
+
assertion.settings = settings
|
77
|
+
assertion.id = generate_unique_id
|
78
|
+
assertion.issue_instant = get_timestamp
|
79
|
+
assertion.issuer = settings.issuer
|
80
|
+
|
81
|
+
attributes.each do |key, value|
|
82
|
+
if assertion.respond_to? "#{key}="
|
83
|
+
assertion.send "#{key}=", value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
assertion.xml = assertion.generate
|
88
|
+
|
89
|
+
assertion
|
90
|
+
end
|
91
|
+
|
92
|
+
def forward_url
|
93
|
+
@forward_url ||= begin
|
94
|
+
url, existing_query_string = destination.split('?')
|
95
|
+
query_string = query_string_append(existing_query_string, query_string_param, base64_assertion)
|
96
|
+
|
97
|
+
if settings.sign?
|
98
|
+
query_string = query_string_append(query_string, "SigAlg", "http://www.w3.org/2000/09/xmldsig#rsa-sha1")
|
99
|
+
signature = generate_signature(query_string, settings.xmlsec_privatekey)
|
100
|
+
query_string = query_string_append(query_string, "Signature", signature)
|
101
|
+
end
|
102
|
+
|
103
|
+
if settings.relay_state
|
104
|
+
query_string = query_string_append(query_string, "RelayState", settings.relay_state)
|
105
|
+
end
|
106
|
+
|
107
|
+
[url, query_string].join("?")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def generate
|
112
|
+
raise "Subclass does not implement abstract method generate."
|
113
|
+
end
|
114
|
+
|
115
|
+
def root_attribute_value(attribute)
|
116
|
+
document.root[attribute] if document
|
117
|
+
end
|
118
|
+
|
119
|
+
def node_attribute_value(xpath, attribute)
|
120
|
+
document.root.find_first(xpath, Onelogin::NAMESPACES)[attribute] rescue nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def node_content(xpath)
|
124
|
+
document.root.find_first(xpath, Onelogin::NAMESPACES).content rescue nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.generate_unique_id(length = 42)
|
128
|
+
chars = ("a".."f").to_a + ("0".."9").to_a
|
129
|
+
chars_len = chars.size
|
130
|
+
unique_id = ("a".."f").to_a[rand(6)]
|
131
|
+
2.upto(length) { |i| unique_id << chars[rand(chars_len)] }
|
132
|
+
unique_id
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.get_timestamp
|
136
|
+
Time.new.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def query_string_param
|
142
|
+
if assertion_type == :request
|
143
|
+
'SAMLRequest'
|
144
|
+
elsif assertion_type == :response
|
145
|
+
'SAMLResponse'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def generate_signature(string, private_key)
|
150
|
+
pkey = OpenSSL::PKey::RSA.new(File.read(private_key))
|
151
|
+
sign = pkey.sign(OpenSSL::Digest::SHA1.new, string)
|
152
|
+
Base64.encode64(sign).gsub(/\s/, '')
|
153
|
+
end
|
154
|
+
|
155
|
+
def query_string_append(query_string, key, value)
|
156
|
+
[query_string, "#{CGI.escape(key)}=#{CGI.escape(value)}"].compact.join('&')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Onelogin::Saml
|
2
|
+
class LogoutRequest < BaseAssertion
|
3
|
+
attr_writer :name_id,
|
4
|
+
:session_index,
|
5
|
+
:name_qualifier,
|
6
|
+
:name_identifier_format
|
7
|
+
|
8
|
+
def name_id
|
9
|
+
@name_id ||= node_content('saml:NameID')
|
10
|
+
end
|
11
|
+
|
12
|
+
def name_identifier_format
|
13
|
+
@name_identifier_format ||= node_attribute_value('samlp:NameID', 'Format')
|
14
|
+
end
|
15
|
+
|
16
|
+
def name_qualifier
|
17
|
+
@name_qualifier ||= node_attribute_value('samlp:NameID', 'NameQualifier')
|
18
|
+
end
|
19
|
+
|
20
|
+
def session_index
|
21
|
+
@session_index ||= node_content('samlp:SessionIndex')
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.generate(name_qualifier, name_id, session_index, settings)
|
25
|
+
super(settings, {
|
26
|
+
destination: settings.idp_slo_target_url,
|
27
|
+
name_identifier_format: settings.name_identifier_format,
|
28
|
+
name_id: name_id,
|
29
|
+
name_qualifier: name_qualifier,
|
30
|
+
session_index: session_index
|
31
|
+
})
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate
|
35
|
+
<<-XML
|
36
|
+
<samlp:LogoutRequest xmlns:samlp="#{Onelogin::NAMESPACES['samlp']}" xmlns:saml="#{Onelogin::NAMESPACES['saml']}" ID="#{self.id}" Version="2.0" IssueInstant="#{self.issue_instant}" Destination="#{self.destination}">
|
37
|
+
<saml:Issuer>#{self.issuer}</saml:Issuer>
|
38
|
+
<saml:NameID NameQualifier="#{self.name_qualifier}" SPNameQualifier="#{self.issuer}" Format="#{self.name_identifier_format}">#{self.name_id}</saml:NameID>
|
39
|
+
<samlp:SessionIndex>#{self.session_index}</samlp:SessionIndex>
|
40
|
+
</samlp:LogoutRequest>
|
41
|
+
XML
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,38 +1,37 @@
|
|
1
1
|
module Onelogin::Saml
|
2
|
-
class LogoutResponse
|
3
|
-
|
4
|
-
attr_reader :settings, :document, :xml, :response
|
5
|
-
attr_reader :status_code, :status_message, :issuer
|
6
|
-
attr_reader :in_response_to, :destination, :request_id
|
7
|
-
def initialize(response, settings=nil)
|
8
|
-
@response = response
|
2
|
+
class LogoutResponse < BaseAssertion
|
9
3
|
|
10
|
-
|
11
|
-
zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
|
12
|
-
@xml = zlib.inflate(@xml)
|
13
|
-
@document = LibXML::XML::Document.string(@xml)
|
4
|
+
STATUS_MESSAGE = 'Successfully Signed Out'
|
14
5
|
|
15
|
-
|
16
|
-
|
17
|
-
@in_response_to = @document.find_first("/samlp:LogoutResponse", Onelogin::NAMESPACES)['InResponseTo'] rescue nil
|
18
|
-
@destination = @document.find_first("/samlp:LogoutResponse", Onelogin::NAMESPACES)['Destination'] rescue nil
|
19
|
-
@status_code = @document.find_first("/samlp:LogoutResponse/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES)['Value'] rescue nil
|
20
|
-
@status_message = @document.find_first("/samlp:LogoutResponse/samlp:Status/samlp:StatusMessage", Onelogin::NAMESPACES).content rescue nil
|
6
|
+
attr_writer :status_code,
|
7
|
+
:status_message
|
21
8
|
|
22
|
-
|
9
|
+
def status_code
|
10
|
+
@status_code ||= node_attribute_value('samlp:Status/samlp:StatusCode', 'Value')
|
23
11
|
end
|
24
12
|
|
25
|
-
def
|
26
|
-
@
|
27
|
-
return unless @response
|
13
|
+
def status_message
|
14
|
+
@status_message ||= node_content("samlp:Status/samlp:StatusMessage")
|
28
15
|
end
|
29
16
|
|
30
|
-
def
|
31
|
-
|
17
|
+
def self.generate(in_response_to, settings)
|
18
|
+
super(settings, in_response_to: in_response_to, destination: settings.idp_slo_target_url)
|
32
19
|
end
|
33
|
-
|
20
|
+
|
21
|
+
def generate
|
22
|
+
<<-XML
|
23
|
+
<samlp:LogoutResponse xmlns:samlp="#{Onelogin::NAMESPACES['samlp']}" xmlns:saml="#{Onelogin::NAMESPACES['saml']}" ID="#{self.id}" Version="2.0" IssueInstant="#{self.issue_instant}" Destination="#{self.destination}" InResponseTo="#{self.in_response_to}">
|
24
|
+
<saml:Issuer>#{self.issuer}</saml:Issuer>
|
25
|
+
<samlp:Status>
|
26
|
+
<samlp:StatusCode Value="#{Onelogin::Saml::StatusCodes::SUCCESS_URI}"></samlp:StatusCode>
|
27
|
+
<samlp:StatusMessage>#{STATUS_MESSAGE}</samlp:StatusMessage>
|
28
|
+
</samlp:Status>
|
29
|
+
</samlp:LogoutResponse>
|
30
|
+
XML
|
31
|
+
end
|
32
|
+
|
34
33
|
def success_status?
|
35
|
-
|
34
|
+
self.status_code == Onelogin::Saml::StatusCodes::SUCCESS_URI
|
36
35
|
end
|
37
36
|
end
|
38
37
|
end
|
@@ -7,6 +7,7 @@ module Onelogin::Saml
|
|
7
7
|
attr_reader :status_code, :status_message
|
8
8
|
attr_reader :in_response_to, :destination, :issuer
|
9
9
|
attr_reader :validation_error
|
10
|
+
|
10
11
|
def initialize(response, settings=nil)
|
11
12
|
@response = response
|
12
13
|
|
@@ -28,6 +29,7 @@ module Onelogin::Saml
|
|
28
29
|
|
29
30
|
def process(settings)
|
30
31
|
@settings = settings
|
32
|
+
@logger = settings.logger
|
31
33
|
return unless @response
|
32
34
|
|
33
35
|
@in_response_to = untrusted_find_first("/samlp:Response")['InResponseTo'] rescue nil
|
@@ -72,10 +74,6 @@ module Onelogin::Saml
|
|
72
74
|
end.flatten.compact
|
73
75
|
end
|
74
76
|
|
75
|
-
def logger=(val)
|
76
|
-
@logger = val
|
77
|
-
end
|
78
|
-
|
79
77
|
def is_valid?
|
80
78
|
@is_valid ||= validate
|
81
79
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Onelogin::Saml
|
2
2
|
class Settings
|
3
|
-
|
3
|
+
|
4
4
|
def initialize(atts={})
|
5
5
|
atts.each do |key, val|
|
6
6
|
if self.respond_to? "#{key}="
|
@@ -8,48 +8,54 @@ module Onelogin::Saml
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
# The URL at which the SAML assertion should be received.
|
13
13
|
attr_accessor :assertion_consumer_service_url
|
14
|
-
|
14
|
+
|
15
15
|
# The name of your application.
|
16
16
|
attr_accessor :issuer
|
17
|
-
|
18
|
-
#
|
17
|
+
|
18
|
+
# Logger
|
19
|
+
attr_accessor :logger
|
20
|
+
|
21
|
+
#
|
19
22
|
attr_accessor :sp_name_qualifier
|
20
|
-
|
23
|
+
|
21
24
|
# The IdP URL to which the authentication request should be sent.
|
22
25
|
attr_accessor :idp_sso_target_url
|
23
|
-
|
26
|
+
|
24
27
|
# The IdP URL to which the logout request should be sent.
|
25
28
|
attr_accessor :idp_slo_target_url
|
26
|
-
|
29
|
+
|
27
30
|
# The certificate fingerprint. This is provided from the identity provider when setting up the relationship.
|
28
31
|
attr_accessor :idp_cert_fingerprint
|
29
|
-
|
32
|
+
|
30
33
|
# Describes the format of the username required by this application.
|
31
34
|
# For email: Onelogin::Saml::NameIdentifiers::EMAIL
|
32
35
|
attr_accessor :name_identifier_format
|
33
|
-
|
36
|
+
|
34
37
|
# The type of authentication requested (see Onelogin::Saml::AuthnContexts)
|
35
38
|
attr_accessor :requested_authn_context
|
36
|
-
|
39
|
+
|
40
|
+
# The relay state to use when generating assertions.
|
41
|
+
attr_accessor :relay_state
|
42
|
+
|
37
43
|
## Attributes for the metadata
|
38
|
-
|
44
|
+
|
39
45
|
# The logout url of your application
|
40
46
|
attr_accessor :sp_slo_url
|
41
|
-
|
42
|
-
# The name of the technical contact for your application
|
47
|
+
|
48
|
+
# The name of the technical contact for your application
|
43
49
|
attr_accessor :tech_contact_name
|
44
|
-
|
50
|
+
|
45
51
|
# The email of the technical contact for your application
|
46
52
|
attr_accessor :tech_contact_email
|
47
|
-
|
53
|
+
|
48
54
|
## Attributes for xml encryption
|
49
55
|
|
50
56
|
# The PEM-encoded certificate
|
51
57
|
attr_accessor :xmlsec_certificate
|
52
|
-
|
58
|
+
|
53
59
|
# The PEM-encoded private key
|
54
60
|
attr_accessor :xmlsec_privatekey
|
55
61
|
|