Class: Signer
- Inherits:
-
Object
- Object
- Signer
- Defined in:
- lib/signer.rb,
lib/signer/version.rb,
lib/signer/digester.rb
Defined Under Namespace
Classes: Digester
Constant Summary collapse
- WSU_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
- WSSE_NAMESPACE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
- VERSION =
'1.4.2'
- DIGEST_ALGORITHMS =
Digest algorithms supported “out of the box”
{ # SHA 1 sha1: { name: 'SHA1', id: 'http://www.w3.org/2000/09/xmldsig#sha1', digester: lambda { OpenSSL::Digest::SHA1.new }, }, # SHA 256 sha256: { name: 'SHA256', id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', digester: lambda { OpenSSL::Digest::SHA256.new }, }, # GOST R 34-11 94 gostr3411: { name: 'GOST R 34.11-94', id: 'http://www.w3.org/2001/04/xmldsig-more#gostr3411', digester: lambda { OpenSSL::Digest.new('md_gost94') }, }, }
Instance Attribute Summary collapse
-
#cert ⇒ Object
Returns the value of attribute cert.
-
#document ⇒ Object
Returns the value of attribute document.
-
#private_key ⇒ Object
Returns the value of attribute private_key.
- #security_node ⇒ Object
- #security_token_id ⇒ Object
-
#signature_algorithm_id ⇒ Object
Returns the value of attribute signature_algorithm_id.
-
#signature_node ⇒ Object
<Signature xmlns=“www.w3.org/2000/09/xmldsig#”>.
Instance Method Summary collapse
-
#binary_security_token_node ⇒ Object
<o:BinarySecurityToken u:Id=“” ValueType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3” EncodingType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary”> …
- #canonicalize(node = document, inclusive_namespaces = nil) ⇒ Object
-
#digest!(target_node, options = {}) ⇒ Object
Digests some
target_node
, which integrity you wish to track. -
#digest_algorithm ⇒ Object
Return symbol name for supported digest algorithms and string name for custom ones.
-
#digest_algorithm=(algorithm) ⇒ Object
Allows to change algorithm for node digesting (default is SHA1).
-
#initialize(document) ⇒ Signer
constructor
A new instance of Signer.
-
#sign!(options = {}) ⇒ Object
Sign document with provided certificate, private key and other options.
-
#signature_digest_algorithm ⇒ Object
Return symbol name for supported digest algorithms and string name for custom ones.
-
#signature_digest_algorithm=(algorithm) ⇒ Object
Allows to change digesting algorithm for signature creation.
-
#signed_info_node ⇒ Object
<SignedInfo> <CanonicalizationMethod Algorithm=“www.w3.org/2001/10/xml-exc-c14n#”/> <SignatureMethod Algorithm=“www.w3.org/2000/09/xmldsig#rsa-sha1”/> …
- #to_xml ⇒ Object
-
#x509_data_node ⇒ Object
<KeyInfo> <X509Data> <X509IssuerSerial> <X509IssuerName>System.Security.Cryptography.X509Certificates.X500DistinguishedName</X509IssuerName> <X509SerialNumber>13070789</X509SerialNumber> </X509IssuerSerial> <X509Certificate>MIID+jCCAuKgAwIBAgIEAMdxxTANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJTRTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMScwJQYDVQQDEx5Ob3JkZWEgcm9sZS1jZXJ0aWZpY2F0ZXMgQ0EgMDExFDASBgNVBAUTCzUxNjQwNi0wMTIwMB4XDTA5MDYxMTEyNTAxOVoXDTExMDYxMTEyNTAxOVowcjELMAkGA1UEBhMCU0UxIDAeBgNVBAMMF05vcmRlYSBEZW1vIENlcnRpZmljYXRlMRQwEgYDVQQEDAtDZXJ0aWZpY2F0ZTEUMBIGA1UEKgwLTm9yZGVhIERlbW8xFTATBgNVBAUTDDAwOTU1NzI0Mzc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcgz5AzbxTbsCE51No7fPnSqmQBIMW9OiPkiHotwYQTl+H9qwDvQRyBqHN26tnw7hNvEShd1ZRGUg4drMEXDV5CmKqsAevs9lauWDaHnGKPNHZJ1hNNYXHwymksEz5zMnG8eqRdhb4vOV2FzreJeYpsgx31Bv0aTofHcHVz4uGcCAwEAAaOCASAwggEcMAkGA1UdEwQCMAAwEQYDVR0OBAoECEj6Y9/vU03WMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEIFjfLBeTpRMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVhLnNlL1JDQTAxMA4GA1UdDwEB/wQEAwIGQDCBiAYDVR0fBIGAMH4wfKB6oHiGdmxkYXA6Ly9sZGFwLm5iLnNlL2NuPU5vcmRlYSUyMHJvbGUtY2VydGlmaWNhdGVzJTIwQ0ElMjAwMSxvPU5vcmRlYSUyMEJhbmslMjBBQiUyMChwdWJsKSxjPVNFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBAEXUv87VpHk51y3TqkMb1MYDqeKvQRE1cNcvhEJhIzdDpXMA9fG0KqvSTT1e0ZI2r78mXDvtTZnpic44jX2XMSmKO6n+1taAXq940tJUhF4arYMUxwDKOso0Doanogug496gipqMlpLgvIhGt06sWjNrvHzp2eGydUFdCsLr2ULqbDcut7g6eMcmrsnrOntjEU/J3hO8gyCeldJ+fI81qarrK/I0MZLR5LWCyVG/SKduoxHLX7JohsbIGyK1qAh9fi8l6X1Rcu80v5inpu71E/DnjbkAZBo7vsj78zzdk7KNliBIqBcIszdJ3dEHRWSI7FspRxyiR0NDm4lpyLwFtfw=</X509Certificate> </X509Data> </KeyInfo>.
Constructor Details
#initialize(document) ⇒ Signer
Returns a new instance of Signer.
17 18 19 20 21 |
# File 'lib/signer.rb', line 17 def initialize(document) self.document = Nokogiri::XML(document.to_s, &:noblanks) self.digest_algorithm = :sha1 self.set_default_signature_method! end |
Instance Attribute Details
#cert ⇒ Object
Returns the value of attribute cert.
11 12 13 |
# File 'lib/signer.rb', line 11 def cert @cert end |
#document ⇒ Object
Returns the value of attribute document.
10 11 12 |
# File 'lib/signer.rb', line 10 def document @document end |
#private_key ⇒ Object
Returns the value of attribute private_key.
10 11 12 |
# File 'lib/signer.rb', line 10 def private_key @private_key end |
#security_node ⇒ Object
71 72 73 |
# File 'lib/signer.rb', line 71 def security_node @security_node ||= document.xpath('//wsse:Security', wsse: WSSE_NAMESPACE).first end |
#security_token_id ⇒ Object
67 68 69 |
# File 'lib/signer.rb', line 67 def security_token_id @security_token_id ||= "uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1" end |
#signature_algorithm_id ⇒ Object
Returns the value of attribute signature_algorithm_id.
10 11 12 |
# File 'lib/signer.rb', line 10 def signature_algorithm_id @signature_algorithm_id end |
#signature_node ⇒ Object
<Signature xmlns=“www.w3.org/2000/09/xmldsig#”>
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/signer.rb', line 80 def signature_node @signature_node ||= begin @signature_node = security_node.at_xpath('ds:Signature', ds: 'http://www.w3.org/2000/09/xmldsig#') unless @signature_node @signature_node = Nokogiri::XML::Node.new('Signature', document) @signature_node.default_namespace = 'http://www.w3.org/2000/09/xmldsig#' security_node.add_child(@signature_node) end @signature_node end end |
Instance Method Details
#binary_security_token_node ⇒ Object
<o:BinarySecurityToken u:Id=“” ValueType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3” EncodingType=“docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary”>
...
</o:BinarySecurityToken> <SignedInfo>
...
</SignedInfo> <KeyInfo>
<o:SecurityTokenReference>
<o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1"/>
</o:SecurityTokenReference>
</KeyInfo>
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/signer.rb', line 123 def binary_security_token_node node = document.at_xpath('wsse:BinarySecurityToken', wsse: WSSE_NAMESPACE) unless node node = Nokogiri::XML::Node.new('BinarySecurityToken', document) node['ValueType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3' node['EncodingType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary' node.content = Base64.encode64(cert.to_der).gsub("\n", '') signature_node.add_previous_sibling(node) wsse_ns = namespace_prefix(node, WSSE_NAMESPACE, 'wsse') wsu_ns = namespace_prefix(node, WSU_NAMESPACE, 'wsu') node["#{wsu_ns}:Id"] = security_token_id key_info_node = Nokogiri::XML::Node.new('KeyInfo', document) security_token_reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:SecurityTokenReference", document) key_info_node.add_child(security_token_reference_node) reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:Reference", document) reference_node['ValueType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3' reference_node['URI'] = "##{security_token_id}" security_token_reference_node.add_child(reference_node) signed_info_node.add_next_sibling(key_info_node) end node end |
#canonicalize(node = document, inclusive_namespaces = nil) ⇒ Object
75 76 77 |
# File 'lib/signer.rb', line 75 def canonicalize(node = document, inclusive_namespaces=nil) node.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, inclusive_namespaces, nil) # The last argument should be exactly +nil+ to remove comments from result end |
#digest!(target_node, options = {}) ⇒ Object
Digests some target_node
, which integrity you wish to track. Any changes in digested node will invalidate signed message. All digest should be calculated before signing.
Available options:
:id
-
Id for the node, if you don’t want to use automatically calculated one
:inclusive_namespaces
-
Array of namespace prefixes which definitions should be added to node during canonicalization
:enveloped
Example of XML that will be inserted in message for call like digest!(node, inclusive_namespaces: ['soap'])
:
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
<ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="soap" />
</Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>aeqXriJuUCk4tPNPAGDXGqHj6ao=</DigestValue>
</Reference>
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/signer.rb', line 202 def digest!(target_node, = {}) wsu_ns = namespace_prefix(target_node, WSU_NAMESPACE) current_id = target_node["#{wsu_ns}:Id"] if wsu_ns id = [:id] || current_id || "_#{Digest::SHA1.hexdigest(target_node.to_s)}" if id.to_s.size > 0 wsu_ns ||= namespace_prefix(target_node, WSU_NAMESPACE, 'wsu') target_node["#{wsu_ns}:Id"] = id.to_s end target_canon = canonicalize(target_node, [:inclusive_namespaces]) target_digest = Base64.encode64(@digester.digest(target_canon)).strip reference_node = Nokogiri::XML::Node.new('Reference', document) reference_node['URI'] = id.to_s.size > 0 ? "##{id}" : "" signed_info_node.add_child(reference_node) transforms_node = Nokogiri::XML::Node.new('Transforms', document) reference_node.add_child(transforms_node) transform_node = Nokogiri::XML::Node.new('Transform', document) if [:enveloped] transform_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature' else transform_node['Algorithm'] = 'http://www.w3.org/2001/10/xml-exc-c14n#' end if [:inclusive_namespaces] inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document) inclusive_namespaces_node.add_namespace_definition('ec', transform_node['Algorithm']) inclusive_namespaces_node['PrefixList'] = [:inclusive_namespaces].join(' ') transform_node.add_child(inclusive_namespaces_node) end transforms_node.add_child(transform_node) digest_method_node = Nokogiri::XML::Node.new('DigestMethod', document) digest_method_node['Algorithm'] = @digester.digest_id reference_node.add_child(digest_method_node) digest_value_node = Nokogiri::XML::Node.new('DigestValue', document) digest_value_node.content = target_digest reference_node.add_child(digest_value_node) self end |
#digest_algorithm ⇒ Object
Return symbol name for supported digest algorithms and string name for custom ones.
28 29 30 |
# File 'lib/signer.rb', line 28 def digest_algorithm @digester.symbol || @digester.digest_name end |
#digest_algorithm=(algorithm) ⇒ Object
Allows to change algorithm for node digesting (default is SHA1).
You may pass either a one of :sha1
, :sha256
or :gostr3411
symbols or Hash
with keys :id
with a string, which will denote algorithm in XML Reference tag and :digester
with instance of class with interface compatible with OpenSSL::Digest
class.
37 38 39 |
# File 'lib/signer.rb', line 37 def digest_algorithm=(algorithm) @digester = Signer::Digester.new(algorithm) end |
#sign!(options = {}) ⇒ Object
Sign document with provided certificate, private key and other options
This should be very last action before calling to_xml
, all the required nodes should be digested with digest!
before signing.
Available options:
:security_token
-
Serializes certificate in DER format, encodes it with Base64 and inserts it within <BinarySecurityToken> tag
:issuer_serial
:inclusive_namespaces
-
Array of namespace prefixes which definitions should be added to signed info node during canonicalization
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/signer.rb', line 254 def sign!( = {}) if [:security_token] binary_security_token_node end if [:issuer_serial] x509_data_node end if [:inclusive_namespaces] c14n_method_node = signed_info_node.at_xpath('ds:CanonicalizationMethod', ds: 'http://www.w3.org/2000/09/xmldsig#') inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document) inclusive_namespaces_node.add_namespace_definition('ec', c14n_method_node['Algorithm']) inclusive_namespaces_node['PrefixList'] = [:inclusive_namespaces].join(' ') c14n_method_node.add_child(inclusive_namespaces_node) end signed_info_canon = canonicalize(signed_info_node, [:inclusive_namespaces]) signature = private_key.sign(@sign_digester.digester, signed_info_canon) signature_value_digest = Base64.encode64(signature).gsub("\n", '') signature_value_node = Nokogiri::XML::Node.new('SignatureValue', document) signature_value_node.content = signature_value_digest signed_info_node.add_next_sibling(signature_value_node) self end |
#signature_digest_algorithm ⇒ Object
Return symbol name for supported digest algorithms and string name for custom ones.
42 43 44 |
# File 'lib/signer.rb', line 42 def signature_digest_algorithm @sign_digester.symbol || @sign_digester.digest_name end |
#signature_digest_algorithm=(algorithm) ⇒ Object
Allows to change digesting algorithm for signature creation. Same as digest_algorithm=
47 48 49 |
# File 'lib/signer.rb', line 47 def signature_digest_algorithm=(algorithm) @sign_digester = Signer::Digester.new(algorithm) end |
#signed_info_node ⇒ Object
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
...
</SignedInfo>
97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/signer.rb', line 97 def signed_info_node node = signature_node.at_xpath('ds:SignedInfo', ds: 'http://www.w3.org/2000/09/xmldsig#') unless node node = Nokogiri::XML::Node.new('SignedInfo', document) signature_node.add_child(node) canonicalization_method_node = Nokogiri::XML::Node.new('CanonicalizationMethod', document) canonicalization_method_node['Algorithm'] = 'http://www.w3.org/2001/10/xml-exc-c14n#' node.add_child(canonicalization_method_node) signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document) signature_method_node['Algorithm'] = self.signature_algorithm_id node.add_child(signature_method_node) end node end |
#to_xml ⇒ Object
23 24 25 |
# File 'lib/signer.rb', line 23 def to_xml document.to_xml(:save_with => 0) end |
#x509_data_node ⇒ Object
<KeyInfo>
<X509Data>
<X509IssuerSerial>
<X509IssuerName>System.Security.Cryptography.X509Certificates.X500DistinguishedName</X509IssuerName>
<X509SerialNumber>13070789</X509SerialNumber>
</X509IssuerSerial>
<X509Certificate>MIID+jCCAuKgAwIBAgIEAMdxxTANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJTRTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMScwJQYDVQQDEx5Ob3JkZWEgcm9sZS1jZXJ0aWZpY2F0ZXMgQ0EgMDExFDASBgNVBAUTCzUxNjQwNi0wMTIwMB4XDTA5MDYxMTEyNTAxOVoXDTExMDYxMTEyNTAxOVowcjELMAkGA1UEBhMCU0UxIDAeBgNVBAMMF05vcmRlYSBEZW1vIENlcnRpZmljYXRlMRQwEgYDVQQEDAtDZXJ0aWZpY2F0ZTEUMBIGA1UEKgwLTm9yZGVhIERlbW8xFTATBgNVBAUTDDAwOTU1NzI0Mzc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcgz5AzbxTbsCE51No7fPnSqmQBIMW9OiPkiHotwYQTl+H9qwDvQRyBqHN26tnw7hNvEShd1ZRGUg4drMEXDV5CmKqsAevs9lauWDaHnGKPNHZJ1hNNYXHwymksEz5zMnG8eqRdhb4vOV2FzreJeYpsgx31Bv0aTofHcHVz4uGcCAwEAAaOCASAwggEcMAkGA1UdEwQCMAAwEQYDVR0OBAoECEj6Y9/vU03WMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEIFjfLBeTpRMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVhLnNlL1JDQTAxMA4GA1UdDwEB/wQEAwIGQDCBiAYDVR0fBIGAMH4wfKB6oHiGdmxkYXA6Ly9sZGFwLm5iLnNlL2NuPU5vcmRlYSUyMHJvbGUtY2VydGlmaWNhdGVzJTIwQ0ElMjAwMSxvPU5vcmRlYSUyMEJhbmslMjBBQiUyMChwdWJsKSxjPVNFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBAEXUv87VpHk51y3TqkMb1MYDqeKvQRE1cNcvhEJhIzdDpXMA9fG0KqvSTT1e0ZI2r78mXDvtTZnpic44jX2XMSmKO6n+1taAXq940tJUhF4arYMUxwDKOso0Doanogug496gipqMlpLgvIhGt06sWjNrvHzp2eGydUFdCsLr2ULqbDcut7g6eMcmrsnrOntjEU/J3hO8gyCeldJ+fI81qarrK/I0MZLR5LWCyVG/SKduoxHLX7JohsbIGyK1qAh9fi8l6X1Rcu80v5inpu71E/DnjbkAZBo7vsj78zzdk7KNliBIqBcIszdJ3dEHRWSI7FspRxyiR0NDm4lpyLwFtfw=</X509Certificate>
</X509Data>
</KeyInfo>
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/signer.rb', line 155 def x509_data_node issuer_name_node = Nokogiri::XML::Node.new('X509IssuerName', document) issuer_name_node.content = "System.Security.Cryptography.X509Certificates.X500DistinguishedName" issuer_number_node = Nokogiri::XML::Node.new('X509SerialNumber', document) issuer_number_node.content = cert.serial issuer_serial_node = Nokogiri::XML::Node.new('X509IssuerSerial', document) issuer_serial_node.add_child(issuer_name_node) issuer_serial_node.add_child(issuer_number_node) cetificate_node = Nokogiri::XML::Node.new('X509Certificate', document) cetificate_node.content = Base64.encode64(cert.to_der).gsub("\n", '') data_node = Nokogiri::XML::Node.new('X509Data', document) data_node.add_child(issuer_serial_node) data_node.add_child(cetificate_node) key_info_node = Nokogiri::XML::Node.new('KeyInfo', document) key_info_node.add_child(data_node) signed_info_node.add_next_sibling(key_info_node) data_node end |