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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 61005075359475ff6b492db62c80f7fea1724bb059c03f19a3f9d04c1f37871f
|
|
4
|
+
data.tar.gz: 0e44c46fe69c8701c7b5441f7a23b6bbf752ad957ae044e5f19d398c01a8db91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ccd8ff5e0c7a4f441dc275a6231924f8969993c7b670b83a60d01f16e4ebf8fff33ccedffaad21a45dc6bca056d1450c5b6566699f9fd733f71fc9342818bec
|
|
7
|
+
data.tar.gz: c0ebff2c3cc4134b84fb52327ee41c7d34bd9b2ab3d89ac7a50a3485bc912ca6936ad64388b04253001106a961f7f2caad313b88a2915e86c7bbd4c516eae673
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Simple SAML
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
data/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/lyrebird)
|
|
4
|
+
|
|
1
5
|
# Lyrebird
|
|
2
6
|
A Ruby gem for mimicking SAML Identity Provider (IdP) responses in test
|
|
3
7
|
environments.
|
|
@@ -84,20 +88,20 @@ end
|
|
|
84
88
|
### Getting the encoded response
|
|
85
89
|
```ruby
|
|
86
90
|
response.mimic # Base64-encoded SAML response (for POST binding)
|
|
87
|
-
response.document #
|
|
91
|
+
response.document # Nokogiri::XML::Document for inspection
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
### Signing
|
|
91
95
|
Sign both the assertion and response with an IdP certificate:
|
|
92
96
|
```ruby
|
|
93
|
-
idp_cert = Lyrebird::Certificate.
|
|
97
|
+
idp_cert = Lyrebird::Certificate.build
|
|
94
98
|
response = Lyrebird::Response.build(sign_with: idp_cert)
|
|
95
99
|
```
|
|
96
100
|
|
|
97
101
|
### Encryption
|
|
98
102
|
Encrypt assertions using the SP's certificate so only the SP can decrypt them:
|
|
99
103
|
```ruby
|
|
100
|
-
sp_cert = Lyrebird::Certificate.
|
|
104
|
+
sp_cert = Lyrebird::Certificate.build
|
|
101
105
|
response = Lyrebird::Response.build(encrypt_with: sp_cert)
|
|
102
106
|
```
|
|
103
107
|
|
|
@@ -121,46 +125,50 @@ Lyrebird::NAMEID_UNSPECIFIED # unspecified
|
|
|
121
125
|
Override defaults globally for all responses/assertions:
|
|
122
126
|
```ruby
|
|
123
127
|
# test/test_helper.rb
|
|
124
|
-
Lyrebird
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
Lyrebird.configure do |d|
|
|
129
|
+
d.issuer = "https://custom.example.com"
|
|
130
|
+
d.recipient = "https://custom.example.com/acs"
|
|
131
|
+
d.audience = "https://custom.example.com"
|
|
132
|
+
d.name_id = "default@example.com"
|
|
133
|
+
d.valid_for = 600 # 10 minutes
|
|
134
|
+
d.attributes = { role: "user" }
|
|
135
|
+
end
|
|
130
136
|
```
|
|
137
|
+
Defaults are frozen after configuration for thread safety.
|
|
131
138
|
|
|
132
139
|
## Certificate
|
|
133
140
|
Generates and manages X.509 certificates for signing SAML responses.
|
|
134
141
|
|
|
135
|
-
###
|
|
142
|
+
### Building a certificate
|
|
136
143
|
```ruby
|
|
137
144
|
# With defaults
|
|
138
|
-
cert = Lyrebird::Certificate.
|
|
145
|
+
cert = Lyrebird::Certificate.build
|
|
139
146
|
|
|
140
147
|
# With options
|
|
141
|
-
cert = Lyrebird::Certificate.
|
|
142
|
-
bits
|
|
143
|
-
cn
|
|
144
|
-
o
|
|
145
|
-
valid_for
|
|
146
|
-
valid_until
|
|
147
|
-
|
|
148
|
+
cert = Lyrebird::Certificate.build do |c|
|
|
149
|
+
c.bits = 4096 # RSA key size (default: 2048)
|
|
150
|
+
c.cn = "example.com" # Common Name
|
|
151
|
+
c.o = "Acme" # Organization
|
|
152
|
+
c.valid_for = 30 # Days (default: 365)
|
|
153
|
+
c.valid_until = Time.new(2999, 12, 31) # Overrides valid_for
|
|
154
|
+
end
|
|
148
155
|
```
|
|
149
156
|
|
|
150
157
|
### Loading an existing certificate
|
|
151
158
|
```ruby
|
|
152
159
|
cert = Lyrebird::Certificate.load(
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
key_pem: File.read("private_key.pem"),
|
|
161
|
+
x509_pem: File.read("certificate.pem")
|
|
155
162
|
)
|
|
156
163
|
```
|
|
157
164
|
|
|
158
|
-
###
|
|
165
|
+
### Using a certificate
|
|
159
166
|
```ruby
|
|
160
|
-
cert.
|
|
161
|
-
cert.
|
|
162
|
-
cert.
|
|
163
|
-
cert.
|
|
164
|
-
cert.
|
|
165
|
-
cert.
|
|
167
|
+
cert.key # OpenSSL::PKey::RSA private key
|
|
168
|
+
cert.x509 # OpenSSL::X509::Certificate object
|
|
169
|
+
cert.key_pem # PEM-encoded private key
|
|
170
|
+
cert.x509_pem # PEM-encoded certificate
|
|
171
|
+
cert.sign(data) # Sign data with RSA-SHA256
|
|
172
|
+
cert.base64 # Base64-encoded certificate (for SAML metadata)
|
|
173
|
+
cert.fingerprint # SHA256 fingerprint
|
|
166
174
|
```
|
data/lib/lyrebird/assertion.rb
CHANGED
|
@@ -28,74 +28,110 @@ module Lyrebird
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def document
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Nokogiri::XML::Document.new.tap do |doc|
|
|
32
|
+
doc.root = root(doc)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
|
-
def root
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
def root(doc)
|
|
39
|
+
doc.create_element("Assertion").tap do |a|
|
|
40
|
+
ns = a.add_namespace_definition("saml", SAML_ASSERTION_NS)
|
|
41
|
+
|
|
42
|
+
a.namespace = ns
|
|
43
|
+
a["ID"] = ID.generate
|
|
44
|
+
a["Version"] = "2.0"
|
|
45
|
+
a["IssueInstant"] = @issue_instant.iso8601
|
|
46
|
+
|
|
47
|
+
a.add_child(doc.create_element("Issuer")).tap do |i|
|
|
48
|
+
i.namespace = ns
|
|
49
|
+
i.content = @issuer
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
a.add_child(subject(doc, ns))
|
|
53
|
+
a.add_child(conditions(doc, ns))
|
|
54
|
+
a.add_child(authn_statement(doc, ns))
|
|
55
|
+
a.add_child(attribute_statement(doc, ns)) if @attributes.any?
|
|
49
56
|
end
|
|
50
57
|
end
|
|
51
58
|
|
|
52
|
-
def subject
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
def subject(doc, ns)
|
|
60
|
+
doc.create_element("Subject").tap do |s|
|
|
61
|
+
s.namespace = ns
|
|
62
|
+
|
|
63
|
+
s.add_child(doc.create_element("NameID")).tap do |nid|
|
|
64
|
+
nid.namespace = ns
|
|
65
|
+
nid["Format"] = @name_id_format
|
|
66
|
+
nid.content = @name_id
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
s.add_child(subject_confirmation(doc, ns))
|
|
58
70
|
end
|
|
59
71
|
end
|
|
60
72
|
|
|
61
|
-
def subject_confirmation
|
|
62
|
-
|
|
63
|
-
sc.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
def subject_confirmation(doc, ns)
|
|
74
|
+
doc.create_element("SubjectConfirmation").tap do |sc|
|
|
75
|
+
sc.namespace = ns
|
|
76
|
+
sc["Method"] = CM_BEARER
|
|
77
|
+
|
|
78
|
+
sc.add_child(doc.create_element("SubjectConfirmationData")).tap do |d|
|
|
79
|
+
d.namespace = ns
|
|
80
|
+
d["NotOnOrAfter"] = @not_on_or_after.iso8601
|
|
81
|
+
d["Recipient"] = @recipient
|
|
82
|
+
d["InResponseTo"] = @in_response_to if @in_response_to
|
|
83
|
+
end
|
|
68
84
|
end
|
|
69
85
|
end
|
|
70
86
|
|
|
71
|
-
def conditions
|
|
72
|
-
|
|
73
|
-
c.
|
|
74
|
-
c
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
def conditions(doc, ns)
|
|
88
|
+
doc.create_element("Conditions").tap do |c|
|
|
89
|
+
c.namespace = ns
|
|
90
|
+
c["NotBefore"] = @not_before.iso8601
|
|
91
|
+
c["NotOnOrAfter"] = @not_on_or_after.iso8601
|
|
92
|
+
|
|
93
|
+
c.add_child(doc.create_element("AudienceRestriction")).tap do |ar|
|
|
94
|
+
ar.namespace = ns
|
|
95
|
+
ar.add_child(doc.create_element("Audience")).tap do |a|
|
|
96
|
+
a.namespace = ns
|
|
97
|
+
a.content = @audience
|
|
98
|
+
end
|
|
99
|
+
end
|
|
77
100
|
end
|
|
78
101
|
end
|
|
79
102
|
|
|
80
|
-
def authn_statement
|
|
81
|
-
|
|
82
|
-
as.
|
|
83
|
-
as
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
def authn_statement(doc, ns)
|
|
104
|
+
doc.create_element("AuthnStatement").tap do |as|
|
|
105
|
+
as.namespace = ns
|
|
106
|
+
as["AuthnInstant"] = @issue_instant.iso8601
|
|
107
|
+
as["SessionIndex"] = ID.generate
|
|
108
|
+
|
|
109
|
+
as.add_child(doc.create_element("AuthnContext")).tap do |ac|
|
|
110
|
+
ac.namespace = ns
|
|
111
|
+
ac.add_child(doc.create_element("AuthnContextClassRef")).tap do |cr|
|
|
112
|
+
cr.namespace = ns
|
|
113
|
+
cr.content = @authn_context
|
|
114
|
+
end
|
|
115
|
+
end
|
|
87
116
|
end
|
|
88
117
|
end
|
|
89
118
|
|
|
90
|
-
def attribute_statement
|
|
91
|
-
|
|
119
|
+
def attribute_statement(doc, ns)
|
|
120
|
+
doc.create_element("AttributeStatement").tap do |as|
|
|
121
|
+
as.namespace = ns
|
|
122
|
+
|
|
92
123
|
@attributes.each do |name, values|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
as.add_child(doc.create_element("Attribute")).tap do |a|
|
|
125
|
+
a.namespace = ns
|
|
126
|
+
a["Name"] = name
|
|
127
|
+
a["NameFormat"] = ATTR_NAME_FORMAT
|
|
96
128
|
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
Array(values).each do |value|
|
|
130
|
+
a.add_child(doc.create_element("AttributeValue")).tap do |av|
|
|
131
|
+
av.namespace = ns
|
|
132
|
+
av.content = value
|
|
133
|
+
end
|
|
134
|
+
end
|
|
99
135
|
end
|
|
100
136
|
end
|
|
101
137
|
end
|
data/lib/lyrebird/certificate.rb
CHANGED
|
@@ -2,62 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
module Lyrebird
|
|
4
4
|
class Certificate
|
|
5
|
-
attr_reader :
|
|
5
|
+
attr_reader :key, :x509
|
|
6
6
|
|
|
7
|
-
def self.
|
|
8
|
-
|
|
7
|
+
def self.build(**kwargs)
|
|
8
|
+
config = OpenStruct.new(kwargs)
|
|
9
|
+
yield config if block_given?
|
|
10
|
+
new(**config.to_h)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def self.load(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
new(
|
|
13
|
+
def self.load(key_pem:, x509_pem:)
|
|
14
|
+
key = OpenSSL::PKey::RSA.new(key_pem)
|
|
15
|
+
x509 = OpenSSL::X509::Certificate.new(x509_pem)
|
|
16
|
+
new(key: key, x509: x509)
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def initialize(
|
|
18
|
-
|
|
20
|
+
bits: 2048,
|
|
19
21
|
cn: nil,
|
|
20
22
|
o: nil,
|
|
21
23
|
valid_for: 365,
|
|
22
24
|
valid_until: nil,
|
|
23
|
-
|
|
25
|
+
key: nil,
|
|
26
|
+
x509: nil
|
|
24
27
|
)
|
|
25
|
-
@private_key = private_key
|
|
26
28
|
@common_name = cn
|
|
27
29
|
@organization = o
|
|
28
30
|
@valid_for = valid_for
|
|
29
31
|
@valid_until = valid_until
|
|
30
|
-
@
|
|
32
|
+
@key = key || OpenSSL::PKey::RSA.new(bits)
|
|
33
|
+
@x509 = x509 || build_x509
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
def
|
|
34
|
-
@
|
|
36
|
+
def key_pem
|
|
37
|
+
@key.to_pem
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
40
|
+
def x509_pem
|
|
41
|
+
@x509.to_pem
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def fingerprint
|
|
42
|
-
OpenSSL::Digest::SHA256.hexdigest(@
|
|
45
|
+
OpenSSL::Digest::SHA256.hexdigest(@x509.to_der)
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
def base64
|
|
46
|
-
Base64.strict_encode64(@
|
|
49
|
+
Base64.strict_encode64(@x509.to_der)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sign(data)
|
|
53
|
+
@key.sign("SHA256", data)
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
private
|
|
50
57
|
|
|
51
|
-
def
|
|
52
|
-
now = Time.now
|
|
58
|
+
def build_x509
|
|
59
|
+
now = Time.now.utc
|
|
53
60
|
|
|
54
61
|
OpenSSL::X509::Certificate.new.tap do |c|
|
|
55
|
-
c.
|
|
62
|
+
c.version = 2
|
|
63
|
+
c.serial = OpenSSL::BN.rand(64)
|
|
64
|
+
c.public_key = @key.public_key
|
|
56
65
|
c.subject = build_subject
|
|
57
66
|
c.issuer = c.subject
|
|
58
67
|
c.not_before = now
|
|
59
68
|
c.not_after = @valid_until || now + (@valid_for * 86_400)
|
|
60
|
-
c.sign(@
|
|
69
|
+
c.sign(@key, OpenSSL::Digest::SHA256.new)
|
|
61
70
|
end
|
|
62
71
|
end
|
|
63
72
|
|
data/lib/lyrebird/defaults.rb
CHANGED
data/lib/lyrebird/encryption.rb
CHANGED
|
@@ -5,70 +5,91 @@ module Lyrebird
|
|
|
5
5
|
def initialize(element, certificate)
|
|
6
6
|
@element = element
|
|
7
7
|
@certificate = certificate
|
|
8
|
+
@doc = element.document
|
|
8
9
|
@aes_key = SecureRandom.random_bytes(32)
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def encrypt
|
|
12
|
-
|
|
13
|
+
build_encrypted_assertion
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
private
|
|
16
17
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
ea.
|
|
20
|
-
ea.
|
|
18
|
+
def build_encrypted_assertion
|
|
19
|
+
@doc.create_element("EncryptedAssertion").tap do |ea|
|
|
20
|
+
@saml = ea.add_namespace_definition("saml", SAML_ASSERTION_NS)
|
|
21
|
+
ea.namespace = @saml
|
|
22
|
+
ea.add_child(build_encrypted_data)
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
ed.
|
|
27
|
-
ed.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
ed.
|
|
31
|
-
|
|
26
|
+
def build_encrypted_data
|
|
27
|
+
@doc.create_element("EncryptedData").tap do |ed|
|
|
28
|
+
@xenc = ed.add_namespace_definition("xenc", XMLENC_NS)
|
|
29
|
+
ed.namespace = @xenc
|
|
30
|
+
ed["Type"] = "#{XMLENC_NS}Element"
|
|
31
|
+
|
|
32
|
+
ed.add_child(@doc.create_element("EncryptionMethod")).tap do |em|
|
|
33
|
+
em.namespace = @xenc
|
|
34
|
+
em["Algorithm"] = AES256_CBC
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
ed.add_child(build_key_info)
|
|
38
|
+
ed.add_child(build_cipher_data)
|
|
32
39
|
end
|
|
33
40
|
end
|
|
34
41
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
ki.
|
|
38
|
-
ki.
|
|
42
|
+
def build_key_info
|
|
43
|
+
@doc.create_element("KeyInfo").tap do |ki|
|
|
44
|
+
@ds = ki.add_namespace_definition("ds", XMLDSIG_NS)
|
|
45
|
+
ki.namespace = @ds
|
|
46
|
+
ki.add_child(build_encrypted_key)
|
|
39
47
|
end
|
|
40
48
|
end
|
|
41
49
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
ek.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ek.
|
|
50
|
+
def build_encrypted_key
|
|
51
|
+
@doc.create_element("EncryptedKey").tap do |ek|
|
|
52
|
+
ek.add_namespace_definition("xenc", XMLENC_NS)
|
|
53
|
+
ek.namespace = @xenc
|
|
54
|
+
|
|
55
|
+
ek.add_child(@doc.create_element("EncryptionMethod")).tap do |em|
|
|
56
|
+
em.namespace = @xenc
|
|
57
|
+
em["Algorithm"] = RSA_OAEP
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ek.add_child(build_encrypted_key_cipher_data)
|
|
48
61
|
end
|
|
49
62
|
end
|
|
50
63
|
|
|
51
|
-
def
|
|
52
|
-
public_key = @certificate.
|
|
64
|
+
def build_encrypted_key_cipher_data
|
|
65
|
+
public_key = @certificate.x509.public_key
|
|
53
66
|
padding = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
|
54
67
|
encrypted_aes_key = public_key.public_encrypt(@aes_key, padding)
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
@doc.create_element("CipherData").tap do |cd|
|
|
70
|
+
cd.namespace = @xenc
|
|
71
|
+
|
|
72
|
+
cd.add_child(@doc.create_element("CipherValue")).tap do |cv|
|
|
73
|
+
cv.namespace = @xenc
|
|
74
|
+
cv.content = Base64.strict_encode64(encrypted_aes_key)
|
|
75
|
+
end
|
|
59
76
|
end
|
|
60
77
|
end
|
|
61
78
|
|
|
62
|
-
def
|
|
79
|
+
def build_cipher_data
|
|
63
80
|
cipher = OpenSSL::Cipher.new("AES-256-CBC")
|
|
64
81
|
cipher.encrypt
|
|
65
82
|
cipher.key = @aes_key
|
|
66
83
|
iv = cipher.random_iv
|
|
67
|
-
ciphertext = cipher.update(@element.
|
|
84
|
+
ciphertext = cipher.update(@element.to_xml) + cipher.final
|
|
85
|
+
|
|
86
|
+
@doc.create_element("CipherData").tap do |cd|
|
|
87
|
+
cd.namespace = @xenc
|
|
68
88
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
cd.add_child(@doc.create_element("CipherValue")).tap do |cv|
|
|
90
|
+
cv.namespace = @xenc
|
|
91
|
+
cv.content = Base64.strict_encode64(iv + ciphertext)
|
|
92
|
+
end
|
|
72
93
|
end
|
|
73
94
|
end
|
|
74
95
|
end
|
data/lib/lyrebird/response.rb
CHANGED
|
@@ -35,44 +35,71 @@ module Lyrebird
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def mimic
|
|
38
|
-
Base64.strict_encode64(document.
|
|
38
|
+
Base64.strict_encode64(document.to_xml(save_with: 0))
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def document
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
Nokogiri::XML::Document.new.tap do |doc|
|
|
43
|
+
doc.root = build_response(doc)
|
|
44
|
+
sign_assertion(doc) if @sign_with && !@encrypt_with
|
|
45
|
+
sign_response(doc) if @sign_with
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
private
|
|
48
50
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
r.
|
|
52
|
-
r.
|
|
53
|
-
|
|
54
|
-
r.
|
|
55
|
-
r
|
|
56
|
-
r
|
|
57
|
-
r
|
|
58
|
-
r
|
|
59
|
-
r
|
|
60
|
-
|
|
61
|
-
|
|
51
|
+
def build_response(doc)
|
|
52
|
+
doc.create_element("Response").tap do |r|
|
|
53
|
+
@samlp = r.add_namespace_definition("samlp", SAML_PROTOCOL_NS)
|
|
54
|
+
@saml = r.add_namespace_definition("saml", SAML_ASSERTION_NS)
|
|
55
|
+
|
|
56
|
+
r.namespace = @samlp
|
|
57
|
+
r["ID"] = ID.generate
|
|
58
|
+
r["Version"] = "2.0"
|
|
59
|
+
r["IssueInstant"] = Time.now.utc.iso8601
|
|
60
|
+
r["Destination"] = @destination if @destination
|
|
61
|
+
r["InResponseTo"] = @in_response_to if @in_response_to
|
|
62
|
+
|
|
63
|
+
r.add_child(doc.create_element("Issuer")).tap do |i|
|
|
64
|
+
i.namespace = @saml
|
|
65
|
+
i.content = @issuer
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
r.add_child(build_status(doc))
|
|
69
|
+
r.add_child(build_assertion_element(doc))
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def build_assertion_element(doc)
|
|
74
|
+
assertion_doc = @assertion.document
|
|
75
|
+
element = assertion_doc.root
|
|
76
|
+
|
|
77
|
+
if @encrypt_with
|
|
78
|
+
Signature.new(element, @sign_with).sign! if @sign_with
|
|
79
|
+
Encryption.new(element, @encrypt_with).encrypt
|
|
80
|
+
else
|
|
81
|
+
element
|
|
62
82
|
end
|
|
63
83
|
end
|
|
64
84
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Encryption.new(element, @encrypt_with).encrypt
|
|
85
|
+
def sign_assertion(doc)
|
|
86
|
+
ns = { "saml" => SAML_ASSERTION_NS }
|
|
87
|
+
assertion = doc.at_xpath("//saml:Assertion", ns)
|
|
88
|
+
Signature.new(assertion, @sign_with).sign!
|
|
70
89
|
end
|
|
71
90
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
def sign_response(doc)
|
|
92
|
+
Signature.new(doc.root, @sign_with).sign!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def build_status(doc)
|
|
96
|
+
doc.create_element("Status").tap do |s|
|
|
97
|
+
s.namespace = @samlp
|
|
98
|
+
|
|
99
|
+
s.add_child(doc.create_element("StatusCode")).tap do |sc|
|
|
100
|
+
sc.namespace = @samlp
|
|
101
|
+
sc["Value"] = STATUS_SUCCESS
|
|
102
|
+
end
|
|
76
103
|
end
|
|
77
104
|
end
|
|
78
105
|
end
|