lyrebird 2.1.0 → 2.1.1
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/lyrebird/assertion.rb +47 -79
- data/lib/lyrebird/certificate.rb +3 -0
- data/lib/lyrebird/{namespaces.rb → constants.rb} +14 -3
- data/lib/lyrebird/defaults.rb +2 -9
- data/lib/lyrebird/encryption.rb +27 -59
- data/lib/lyrebird/response.rb +25 -50
- data/lib/lyrebird/signature.rb +36 -80
- data/lib/lyrebird/version.rb +1 -1
- data/lib/lyrebird.rb +1 -1
- metadata +3 -73
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54582bda5673179b49a58b405ee52668486162eb785d728cfd98b6dcaed7f1cf
|
|
4
|
+
data.tar.gz: f6e2048c4adb1a6b3690f2517c781cd431f9cc0271217df6effb8944e808fee0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5998bda9158b2d8d3f64f6260fcfb9b3b06c62469c923e86776bac8ab541f955945621f799b23ab69cc4388e561d403ab15d8adc763b018f5fad9411409668c8
|
|
7
|
+
data.tar.gz: 9aebec46c7f33d74c5274073dea1a93a347fb363b603bbf3cd8a8dc7ba26f0fbe2c9e5f34ba6fc4afcb67bfe850e8c963d485a2adca3539bd3137173d4675bba
|
data/lib/lyrebird/assertion.rb
CHANGED
|
@@ -28,112 +28,80 @@ module Lyrebird
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def document
|
|
31
|
-
@document ||= Nokogiri::XML::
|
|
32
|
-
doc.root = root(doc)
|
|
33
|
-
end
|
|
31
|
+
@document ||= Nokogiri::XML::Builder.new { |xml| root(xml) }.doc
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
private
|
|
37
35
|
|
|
38
|
-
def root(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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?
|
|
36
|
+
def root(xml)
|
|
37
|
+
attrs = {
|
|
38
|
+
"xmlns:saml" => SAML_ASSERTION_NS,
|
|
39
|
+
ID: ID.generate,
|
|
40
|
+
Version: "2.0",
|
|
41
|
+
IssueInstant: @issue_instant.iso8601
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
xml["saml"].Assertion(attrs) do
|
|
45
|
+
xml["saml"].Issuer(@issuer.to_s)
|
|
46
|
+
subject(xml)
|
|
47
|
+
conditions(xml)
|
|
48
|
+
authn_statement(xml)
|
|
49
|
+
attribute_statement(xml) if @attributes.any?
|
|
56
50
|
end
|
|
57
51
|
end
|
|
58
52
|
|
|
59
|
-
def subject(
|
|
60
|
-
|
|
61
|
-
|
|
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))
|
|
53
|
+
def subject(xml)
|
|
54
|
+
xml["saml"].Subject do
|
|
55
|
+
xml["saml"].NameID(@name_id.to_s, Format: @name_id_format)
|
|
56
|
+
subject_confirmation(xml)
|
|
70
57
|
end
|
|
71
58
|
end
|
|
72
59
|
|
|
73
|
-
def subject_confirmation(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
def subject_confirmation(xml)
|
|
61
|
+
attrs = {
|
|
62
|
+
NotOnOrAfter: @not_on_or_after.iso8601,
|
|
63
|
+
Recipient: @recipient,
|
|
64
|
+
InResponseTo: @in_response_to
|
|
65
|
+
}.compact
|
|
77
66
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
d["NotOnOrAfter"] = @not_on_or_after.iso8601
|
|
81
|
-
d["Recipient"] = @recipient
|
|
82
|
-
d["InResponseTo"] = @in_response_to if @in_response_to
|
|
83
|
-
end
|
|
67
|
+
xml["saml"].SubjectConfirmation(Method: CM_BEARER) do
|
|
68
|
+
xml["saml"].SubjectConfirmationData(attrs)
|
|
84
69
|
end
|
|
85
70
|
end
|
|
86
71
|
|
|
87
|
-
def conditions(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
72
|
+
def conditions(xml)
|
|
73
|
+
attrs = {
|
|
74
|
+
NotBefore: @not_before.iso8601,
|
|
75
|
+
NotOnOrAfter: @not_on_or_after.iso8601
|
|
76
|
+
}
|
|
92
77
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
a.namespace = ns
|
|
97
|
-
a.content = @audience
|
|
98
|
-
end
|
|
78
|
+
xml["saml"].Conditions(attrs) do
|
|
79
|
+
xml["saml"].AudienceRestriction do
|
|
80
|
+
xml["saml"].Audience(@audience.to_s)
|
|
99
81
|
end
|
|
100
82
|
end
|
|
101
83
|
end
|
|
102
84
|
|
|
103
|
-
def authn_statement(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
def authn_statement(xml)
|
|
86
|
+
attrs = {
|
|
87
|
+
AuthnInstant: @issue_instant.iso8601,
|
|
88
|
+
SessionIndex: ID.generate
|
|
89
|
+
}
|
|
108
90
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cr.namespace = ns
|
|
113
|
-
cr.content = @authn_context
|
|
114
|
-
end
|
|
91
|
+
xml["saml"].AuthnStatement(attrs) do
|
|
92
|
+
xml["saml"].AuthnContext do
|
|
93
|
+
xml["saml"].AuthnContextClassRef(@authn_context.to_s)
|
|
115
94
|
end
|
|
116
95
|
end
|
|
117
96
|
end
|
|
118
97
|
|
|
119
|
-
def attribute_statement(
|
|
120
|
-
|
|
121
|
-
as.namespace = ns
|
|
122
|
-
|
|
98
|
+
def attribute_statement(xml)
|
|
99
|
+
xml["saml"].AttributeStatement do
|
|
123
100
|
@attributes.each do |name, values|
|
|
124
101
|
list = values.is_a?(Enumerable) ? values.to_a : [values].compact
|
|
125
102
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
a["Name"] = name
|
|
129
|
-
a["NameFormat"] = ATTR_NAME_FORMAT
|
|
130
|
-
|
|
131
|
-
list.each do |value|
|
|
132
|
-
a.add_child(doc.create_element("AttributeValue")).tap do |av|
|
|
133
|
-
av.namespace = ns
|
|
134
|
-
av.content = value
|
|
135
|
-
end
|
|
136
|
-
end
|
|
103
|
+
xml["saml"].Attribute(Name: name, NameFormat: ATTR_NAME_FORMAT) do
|
|
104
|
+
list.each { |value| xml["saml"].AttributeValue(value.to_s) }
|
|
137
105
|
end
|
|
138
106
|
end
|
|
139
107
|
end
|
data/lib/lyrebird/certificate.rb
CHANGED
|
@@ -3,14 +3,25 @@
|
|
|
3
3
|
module Lyrebird
|
|
4
4
|
SAML_ASSERTION_NS = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
5
5
|
SAML_PROTOCOL_NS = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
6
|
+
|
|
7
|
+
NAMEID_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
8
|
+
NAMEID_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
|
9
|
+
NAMEID_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
|
10
|
+
NAMEID_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
|
11
|
+
|
|
12
|
+
ppt = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
|
13
|
+
AUTHN_PASSWORD_PROTECTED_TRANSPORT = ppt
|
|
14
|
+
|
|
15
|
+
CM_BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
|
16
|
+
ATTR_NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
|
|
17
|
+
STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
18
|
+
|
|
6
19
|
XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#"
|
|
7
20
|
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
|
8
21
|
EXC_C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
|
9
22
|
SHA256_DIGEST = "http://www.w3.org/2001/04/xmlenc#sha256"
|
|
10
23
|
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
|
11
|
-
|
|
12
|
-
ATTR_NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
|
|
13
|
-
STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
24
|
+
|
|
14
25
|
XMLENC_NS = "http://www.w3.org/2001/04/xmlenc#"
|
|
15
26
|
AES256_CBC = "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
|
16
27
|
RSA_OAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
|
data/lib/lyrebird/defaults.rb
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Lyrebird
|
|
4
|
-
NAMEID_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
5
|
-
NAMEID_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
|
6
|
-
NAMEID_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
|
7
|
-
NAMEID_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
|
8
|
-
|
|
9
4
|
class Defaults
|
|
10
5
|
attr_accessor :issuer
|
|
11
6
|
attr_accessor :name_id
|
|
@@ -26,13 +21,11 @@ module Lyrebird
|
|
|
26
21
|
@valid_for = 300 # 5 minutes
|
|
27
22
|
@audience = "https://sp.example.com"
|
|
28
23
|
|
|
29
|
-
@authn_context =
|
|
30
|
-
"urn:oasis:names:tc:SAML:2.0:ac:classes:" \
|
|
31
|
-
"PasswordProtectedTransport"
|
|
24
|
+
@authn_context = AUTHN_PASSWORD_PROTECTED_TRANSPORT
|
|
32
25
|
|
|
33
26
|
@attributes = {
|
|
34
27
|
first_name: "Test",
|
|
35
|
-
last_name: "User"
|
|
28
|
+
last_name: "User"
|
|
36
29
|
}.freeze
|
|
37
30
|
end
|
|
38
31
|
end
|
data/lib/lyrebird/encryption.rb
CHANGED
|
@@ -5,92 +5,60 @@ module Lyrebird
|
|
|
5
5
|
def initialize(element, certificate)
|
|
6
6
|
@element = element
|
|
7
7
|
@certificate = certificate
|
|
8
|
-
@doc = element.document
|
|
9
8
|
@aes_key = SecureRandom.random_bytes(32)
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def encrypt
|
|
13
|
-
|
|
12
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
13
|
+
xml["saml"].EncryptedAssertion("xmlns:saml" => SAML_ASSERTION_NS) do
|
|
14
|
+
encrypted_data(xml)
|
|
15
|
+
end
|
|
16
|
+
end.doc.root
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
private
|
|
17
20
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
24
|
-
end
|
|
21
|
+
def encrypted_data(xml)
|
|
22
|
+
attrs = {
|
|
23
|
+
"xmlns:xenc" => XMLENC_NS,
|
|
24
|
+
Type: "#{XMLENC_NS}Element"
|
|
25
|
+
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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)
|
|
27
|
+
xml["xenc"].EncryptedData(attrs) do
|
|
28
|
+
xml["xenc"].EncryptionMethod(Algorithm: AES256_CBC)
|
|
29
|
+
key_info(xml)
|
|
30
|
+
cipher_data(xml, ciphertext)
|
|
39
31
|
end
|
|
40
32
|
end
|
|
41
33
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
def key_info(xml)
|
|
35
|
+
xml["ds"].KeyInfo("xmlns:ds" => XMLDSIG_NS) do
|
|
36
|
+
xml["xenc"].EncryptedKey do
|
|
37
|
+
xml["xenc"].EncryptionMethod(Algorithm: RSA_OAEP)
|
|
38
|
+
cipher_data(xml, wrapped_key)
|
|
39
|
+
end
|
|
47
40
|
end
|
|
48
41
|
end
|
|
49
42
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ek.add_child(@doc.create_element("EncryptionMethod")).tap do |em|
|
|
55
|
-
em.namespace = @xenc
|
|
56
|
-
em["Algorithm"] = RSA_OAEP
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
ek.add_child(build_encrypted_key_cipher_data)
|
|
43
|
+
def cipher_data(xml, value)
|
|
44
|
+
xml["xenc"].CipherData do
|
|
45
|
+
xml["xenc"].CipherValue(value)
|
|
60
46
|
end
|
|
61
47
|
end
|
|
62
48
|
|
|
63
|
-
def
|
|
49
|
+
def wrapped_key
|
|
64
50
|
public_key = @certificate.x509.public_key
|
|
65
51
|
padding = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@doc.create_element("CipherData").tap do |cd|
|
|
69
|
-
cd.namespace = @xenc
|
|
70
|
-
|
|
71
|
-
cd.add_child(@doc.create_element("CipherValue")).tap do |cv|
|
|
72
|
-
cv.namespace = @xenc
|
|
73
|
-
cv.content = [encrypted_aes_key].pack("m0")
|
|
74
|
-
end
|
|
75
|
-
end
|
|
52
|
+
[public_key.public_encrypt(@aes_key, padding)].pack("m0")
|
|
76
53
|
end
|
|
77
54
|
|
|
78
|
-
def
|
|
55
|
+
def ciphertext
|
|
79
56
|
cipher = OpenSSL::Cipher.new("AES-256-CBC")
|
|
80
57
|
cipher.encrypt
|
|
81
58
|
cipher.key = @aes_key
|
|
82
59
|
iv = cipher.random_iv
|
|
83
60
|
plaintext = @element.to_xml(save_with: 0)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
@doc.create_element("CipherData").tap do |cd|
|
|
87
|
-
cd.namespace = @xenc
|
|
88
|
-
|
|
89
|
-
cd.add_child(@doc.create_element("CipherValue")).tap do |cv|
|
|
90
|
-
cv.namespace = @xenc
|
|
91
|
-
cv.content = [iv + ciphertext].pack("m0")
|
|
92
|
-
end
|
|
93
|
-
end
|
|
61
|
+
[iv + cipher.update(plaintext) + cipher.final].pack("m0")
|
|
94
62
|
end
|
|
95
63
|
end
|
|
96
64
|
end
|
data/lib/lyrebird/response.rb
CHANGED
|
@@ -48,67 +48,42 @@ module Lyrebird
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def document
|
|
51
|
-
@document ||=
|
|
52
|
-
doc.root
|
|
53
|
-
sign_assertion(doc) if @sign_with && !@encrypt_with
|
|
54
|
-
sign_response(doc) if @sign_with
|
|
51
|
+
@document ||= build_response.doc.tap do |doc|
|
|
52
|
+
Signature.new(doc.root, @sign_with).sign! if @sign_with
|
|
55
53
|
end
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
private
|
|
59
57
|
|
|
60
|
-
def build_response
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
def build_response
|
|
59
|
+
attrs = {
|
|
60
|
+
"xmlns:samlp" => SAML_PROTOCOL_NS,
|
|
61
|
+
"xmlns:saml" => SAML_ASSERTION_NS,
|
|
62
|
+
ID: ID.generate,
|
|
63
|
+
Version: "2.0",
|
|
64
|
+
IssueInstant: Time.now.utc.iso8601,
|
|
65
|
+
Destination: @destination,
|
|
66
|
+
InResponseTo: @in_response_to
|
|
67
|
+
}.compact
|
|
68
|
+
|
|
69
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
70
|
+
xml["samlp"].Response(attrs) do
|
|
71
|
+
xml["saml"].Issuer(@issuer.to_s)
|
|
72
|
+
|
|
73
|
+
xml["samlp"].Status do
|
|
74
|
+
xml["samlp"].StatusCode(Value: STATUS_SUCCESS)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
xml.parent << build_assertion_element
|
|
75
78
|
end
|
|
76
|
-
|
|
77
|
-
r.add_child(build_status(doc))
|
|
78
|
-
r.add_child(build_assertion_element)
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def build_assertion_element
|
|
83
83
|
element = @assertion.document.root
|
|
84
|
-
|
|
85
|
-
if @encrypt_with
|
|
86
|
-
|
|
87
|
-
Encryption.new(element, @encrypt_with).encrypt
|
|
88
|
-
else
|
|
89
|
-
element
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def sign_assertion(doc)
|
|
94
|
-
ns = { "saml" => SAML_ASSERTION_NS }
|
|
95
|
-
assertion = doc.at_xpath("//saml:Assertion", ns)
|
|
96
|
-
Signature.new(assertion, @sign_with).sign!
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def sign_response(doc)
|
|
100
|
-
Signature.new(doc.root, @sign_with).sign!
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def build_status(doc)
|
|
104
|
-
doc.create_element("Status").tap do |s|
|
|
105
|
-
s.namespace = @samlp
|
|
106
|
-
|
|
107
|
-
s.add_child(doc.create_element("StatusCode")).tap do |sc|
|
|
108
|
-
sc.namespace = @samlp
|
|
109
|
-
sc["Value"] = STATUS_SUCCESS
|
|
110
|
-
end
|
|
111
|
-
end
|
|
84
|
+
Signature.new(element, @sign_with).sign! if @sign_with
|
|
85
|
+
element = Encryption.new(element, @encrypt_with).encrypt if @encrypt_with
|
|
86
|
+
element
|
|
112
87
|
end
|
|
113
88
|
end
|
|
114
89
|
end
|
data/lib/lyrebird/signature.rb
CHANGED
|
@@ -4,112 +4,59 @@ module Lyrebird
|
|
|
4
4
|
class Signature
|
|
5
5
|
C14N_EXCLUSIVE = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
|
6
6
|
|
|
7
|
+
private_constant :C14N_EXCLUSIVE
|
|
8
|
+
|
|
7
9
|
def initialize(element, certificate)
|
|
8
10
|
@element = element
|
|
9
11
|
@certificate = certificate
|
|
10
|
-
@doc = element.document
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def sign!
|
|
14
15
|
ns = { "saml" => SAML_ASSERTION_NS, "ds" => XMLDSIG_NS }
|
|
15
16
|
@element.at_xpath("ds:Signature", ns)&.remove
|
|
17
|
+
signature = build_signature
|
|
16
18
|
issuer = @element.at_xpath("saml:Issuer", ns)
|
|
17
|
-
issuer.add_next_sibling(
|
|
18
|
-
populate_signature_value
|
|
19
|
+
issuer.add_next_sibling(signature)
|
|
20
|
+
populate_signature_value(signature)
|
|
19
21
|
self
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
private
|
|
23
25
|
|
|
24
26
|
def build_signature
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
sig.add_child(build_signature_value)
|
|
31
|
-
sig.add_child(build_key_info)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def build_signed_info
|
|
36
|
-
@doc.create_element("SignedInfo").tap do |si|
|
|
37
|
-
@signed_info = si
|
|
38
|
-
si.namespace = @ds
|
|
39
|
-
|
|
40
|
-
si.add_child(@doc.create_element("CanonicalizationMethod")).tap do |cm|
|
|
41
|
-
cm.namespace = @ds
|
|
42
|
-
cm["Algorithm"] = EXC_C14N
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
si.add_child(@doc.create_element("SignatureMethod")).tap do |sm|
|
|
46
|
-
sm.namespace = @ds
|
|
47
|
-
sm["Algorithm"] = RSA_SHA256
|
|
27
|
+
Nokogiri::XML::Builder.new do |xml|
|
|
28
|
+
xml["ds"].Signature("xmlns:ds" => XMLDSIG_NS) do
|
|
29
|
+
signed_info(xml)
|
|
30
|
+
xml["ds"].SignatureValue
|
|
31
|
+
key_info(xml)
|
|
48
32
|
end
|
|
49
|
-
|
|
50
|
-
si.add_child(build_reference)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def build_signature_value
|
|
55
|
-
@doc.create_element("SignatureValue").tap do |sv|
|
|
56
|
-
sv.namespace = @ds
|
|
57
|
-
@signature_value = sv
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def populate_signature_value
|
|
62
|
-
canonical = @signed_info.canonicalize(C14N_EXCLUSIVE)
|
|
63
|
-
sig = @certificate.key.sign(OpenSSL::Digest.new("SHA256"), canonical)
|
|
64
|
-
@signature_value.content = [sig].pack("m0")
|
|
33
|
+
end.doc.root
|
|
65
34
|
end
|
|
66
35
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
xd.namespace = @ds
|
|
73
|
-
|
|
74
|
-
xd.add_child(@doc.create_element("X509Certificate")).tap do |xc|
|
|
75
|
-
xc.namespace = @ds
|
|
76
|
-
xc.content = @certificate.base64
|
|
77
|
-
end
|
|
78
|
-
end
|
|
36
|
+
def signed_info(xml)
|
|
37
|
+
xml["ds"].SignedInfo do
|
|
38
|
+
xml["ds"].CanonicalizationMethod(Algorithm: EXC_C14N)
|
|
39
|
+
xml["ds"].SignatureMethod(Algorithm: RSA_SHA256)
|
|
40
|
+
reference(xml)
|
|
79
41
|
end
|
|
80
42
|
end
|
|
81
43
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
ref.add_child(build_transforms)
|
|
88
|
-
|
|
89
|
-
ref.add_child(@doc.create_element("DigestMethod")).tap do |dm|
|
|
90
|
-
dm.namespace = @ds
|
|
91
|
-
dm["Algorithm"] = SHA256_DIGEST
|
|
44
|
+
def reference(xml)
|
|
45
|
+
xml["ds"].Reference(URI: "##{@element["ID"]}") do
|
|
46
|
+
xml["ds"].Transforms do
|
|
47
|
+
xml["ds"].Transform(Algorithm: ENVELOPED_SIG)
|
|
48
|
+
xml["ds"].Transform(Algorithm: EXC_C14N)
|
|
92
49
|
end
|
|
93
50
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
dv.content = compute_digest
|
|
97
|
-
end
|
|
51
|
+
xml["ds"].DigestMethod(Algorithm: SHA256_DIGEST)
|
|
52
|
+
xml["ds"].DigestValue(compute_digest)
|
|
98
53
|
end
|
|
99
54
|
end
|
|
100
55
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
t.add_child(@doc.create_element("Transform")).tap do |tr|
|
|
106
|
-
tr.namespace = @ds
|
|
107
|
-
tr["Algorithm"] = ENVELOPED_SIG
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
t.add_child(@doc.create_element("Transform")).tap do |tr|
|
|
111
|
-
tr.namespace = @ds
|
|
112
|
-
tr["Algorithm"] = EXC_C14N
|
|
56
|
+
def key_info(xml)
|
|
57
|
+
xml["ds"].KeyInfo do
|
|
58
|
+
xml["ds"].X509Data do
|
|
59
|
+
xml["ds"].X509Certificate(@certificate.base64)
|
|
113
60
|
end
|
|
114
61
|
end
|
|
115
62
|
end
|
|
@@ -119,5 +66,14 @@ module Lyrebird
|
|
|
119
66
|
digest = OpenSSL::Digest.new("SHA256").digest(canonical)
|
|
120
67
|
[digest].pack("m0")
|
|
121
68
|
end
|
|
69
|
+
|
|
70
|
+
def populate_signature_value(signature)
|
|
71
|
+
ns = { "ds" => XMLDSIG_NS }
|
|
72
|
+
signed_info = signature.at_xpath("ds:SignedInfo", ns)
|
|
73
|
+
canonical = signed_info.canonicalize(C14N_EXCLUSIVE)
|
|
74
|
+
sig = @certificate.key.sign(OpenSSL::Digest.new("SHA256"), canonical)
|
|
75
|
+
value = signature.at_xpath("ds:SignatureValue", ns)
|
|
76
|
+
value.content = [sig].pack("m0")
|
|
77
|
+
end
|
|
122
78
|
end
|
|
123
79
|
end
|
data/lib/lyrebird/version.rb
CHANGED
data/lib/lyrebird.rb
CHANGED
|
@@ -7,10 +7,10 @@ require "time"
|
|
|
7
7
|
|
|
8
8
|
require_relative "lyrebird/assertion"
|
|
9
9
|
require_relative "lyrebird/certificate"
|
|
10
|
+
require_relative "lyrebird/constants"
|
|
10
11
|
require_relative "lyrebird/defaults"
|
|
11
12
|
require_relative "lyrebird/encryption"
|
|
12
13
|
require_relative "lyrebird/id"
|
|
13
|
-
require_relative "lyrebird/namespaces"
|
|
14
14
|
require_relative "lyrebird/options"
|
|
15
15
|
require_relative "lyrebird/response"
|
|
16
16
|
require_relative "lyrebird/signature"
|
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: 2.1.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Josh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -24,76 +24,6 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.14'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: base64
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: logger
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: minitest
|
|
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'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: rake
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: ruby-saml
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
27
|
description:
|
|
98
28
|
email:
|
|
99
29
|
executables: []
|
|
@@ -105,10 +35,10 @@ files:
|
|
|
105
35
|
- lib/lyrebird.rb
|
|
106
36
|
- lib/lyrebird/assertion.rb
|
|
107
37
|
- lib/lyrebird/certificate.rb
|
|
38
|
+
- lib/lyrebird/constants.rb
|
|
108
39
|
- lib/lyrebird/defaults.rb
|
|
109
40
|
- lib/lyrebird/encryption.rb
|
|
110
41
|
- lib/lyrebird/id.rb
|
|
111
|
-
- lib/lyrebird/namespaces.rb
|
|
112
42
|
- lib/lyrebird/options.rb
|
|
113
43
|
- lib/lyrebird/response.rb
|
|
114
44
|
- lib/lyrebird/signature.rb
|