@@ -27,12 +27,6 @@ class Document < BaseDocument
2727 SHA512 = RubySaml ::XML ::Crypto ::SHA512
2828 ENVELOPED_SIG = RubySaml ::XML ::Crypto ::ENVELOPED_SIG
2929
30- attr_writer :uuid
31-
32- def uuid
33- @uuid ||= document . root &.attributes &.[]( 'ID' )
34- end
35-
3630 # <Signature>
3731 # <SignedInfo>
3832 # <CanonicalizationMethod />
@@ -48,70 +42,114 @@ def uuid
4842 # <KeyInfo />
4943 # <Object />
5044 # </Signature>
51- def sign_document ( private_key , certificate , signature_method = RSA_SHA256 , digest_method = SHA256 )
45+ def sign_document ( private_key , certificate , signature_method = RubySaml ::XML ::Crypto ::RSA_SHA256 , digest_method = RubySaml ::XML ::Crypto ::SHA256 )
46+ signature_element = build_signature_element ( private_key , certificate , signature_method , digest_method )
47+ signature_element = convert_nokogiri_to_rexml ( signature_element )
48+ issuer_element = elements [ '//saml:Issuer' ]
49+ if issuer_element
50+ root . insert_after ( issuer_element , signature_element )
51+ elsif ( first_child = root . children [ 0 ] )
52+ root . insert_before ( first_child , signature_element )
53+ else
54+ root . add_element ( signature_element )
55+ end
56+ end
57+
58+ private
59+
60+ def build_signature_element ( private_key , certificate , signature_method , digest_method )
61+ # Parse the original document
5262 noko = Nokogiri ::XML ( to_s ) do |config |
5363 config . options = RubySaml ::XML ::BaseDocument ::NOKOGIRI_OPTIONS
5464 end
5565
56- signature_element = REXML ::Element . new ( 'ds:Signature' ) . add_namespace ( 'ds' , RubySaml ::XML ::Crypto ::DSIG )
57- signed_info_element = signature_element . add_element ( 'ds:SignedInfo' )
58- signed_info_element . add_element ( 'ds:CanonicalizationMethod' , { 'Algorithm' => RubySaml ::XML ::Crypto ::C14N } )
59- signed_info_element . add_element ( 'ds:SignatureMethod' , { 'Algorithm' => signature_method } )
66+ # Build the Signature element
67+ signature_element = Nokogiri ::XML ::Builder . new do |xml |
68+ xml [ 'ds' ] . Signature ( 'xmlns:ds' => RubySaml ::XML ::Crypto ::DSIG ) do
69+ xml [ 'ds' ] . SignedInfo do
70+ xml [ 'ds' ] . CanonicalizationMethod ( Algorithm : RubySaml ::XML ::Crypto ::C14N )
71+ xml [ 'ds' ] . SignatureMethod ( Algorithm : signature_method )
72+ xml [ 'ds' ] . Reference ( URI : "##{ noko . root . attr ( 'ID' ) } " ) do
73+ xml [ 'ds' ] . Transforms do
74+ xml [ 'ds' ] . Transform ( Algorithm : RubySaml ::XML ::Crypto ::ENVELOPED_SIG )
75+ xml [ 'ds' ] . Transform ( Algorithm : RubySaml ::XML ::Crypto ::C14N ) do
76+ xml [ 'ec' ] . InclusiveNamespaces (
77+ 'xmlns:ec' => RubySaml ::XML ::Crypto ::C14N ,
78+ PrefixList : INC_PREFIX_LIST
79+ )
80+ end
81+ end
82+ xml [ 'ds' ] . DigestMethod ( Algorithm : digest_method )
83+ xml [ 'ds' ] . DigestValue ( digest_value ( noko , digest_method ) )
84+ end
85+ end
86+ xml [ 'ds' ] . SignatureValue # Value is added below
87+ xml [ 'ds' ] . KeyInfo do
88+ xml [ 'ds' ] . X509Data do
89+ xml [ 'ds' ] . X509Certificate ( certificate_value ( certificate ) )
90+ end
91+ end
92+ end
93+ end . doc . root
94+
95+ # Set the signature value
96+ signed_info_element = signature_element . at_xpath ( '//ds:SignedInfo' , 'ds' => RubySaml ::XML ::Crypto ::DSIG )
97+ sig_value_element = signature_element . at_xpath ( '//ds:SignatureValue' , 'ds' => RubySaml ::XML ::Crypto ::DSIG )
98+ sig_value_element . content = signature_value ( signed_info_element , private_key , signature_method )
99+
100+ signature_element
101+ end
60102
61- # Add Reference
62- reference_element = signed_info_element . add_element ( 'ds:Reference' , { 'URI' => "##{ uuid } " } )
103+ def digest_value ( document , digest_method )
104+ inclusive_namespaces = INC_PREFIX_LIST . split
105+ canon_algorithm = RubySaml ::XML ::Crypto . canon_algorithm ( RubySaml ::XML ::Crypto ::C14N )
106+ hash_algorithm = RubySaml ::XML ::Crypto . hash_algorithm ( digest_method )
63107
64- # Add Transforms
65- transforms_element = reference_element . add_element ( 'ds:Transforms' )
66- transforms_element . add_element ( 'ds:Transform' , { 'Algorithm' => RubySaml ::XML ::Crypto ::ENVELOPED_SIG } )
67- c14element = transforms_element . add_element ( 'ds:Transform' , { 'Algorithm' => RubySaml ::XML ::Crypto ::C14N } )
68- c14element . add_element ( 'ec:InclusiveNamespaces' , { 'xmlns:ec' => RubySaml ::XML ::Crypto ::C14N , 'PrefixList' => INC_PREFIX_LIST } )
108+ canon_doc = document . canonicalize ( canon_algorithm , inclusive_namespaces )
109+ Base64 . encode64 ( hash_algorithm . digest ( canon_doc ) ) . strip
110+ end
69111
70- digest_method_element = reference_element . add_element ( 'ds:DigestMethod' , { 'Algorithm' => digest_method } )
71- inclusive_namespaces = INC_PREFIX_LIST . split
72- canon_doc = noko . canonicalize ( RubySaml ::XML ::Crypto . canon_algorithm ( RubySaml ::XML ::Crypto ::C14N ) , inclusive_namespaces )
73- reference_element . add_element ( 'ds:DigestValue' ) . text = compute_digest ( canon_doc , RubySaml ::XML ::Crypto . hash_algorithm ( digest_method_element ) )
112+ def signature_value ( signed_info_element , private_key , signature_method )
113+ canon_algorithm = RubySaml ::XML ::Crypto . canon_algorithm ( RubySaml ::XML ::Crypto ::C14N )
114+ hash_algorithm = RubySaml ::XML ::Crypto . hash_algorithm ( signature_method ) . new
74115
75- # add SignatureValue
76- noko_sig_element = Nokogiri ::XML ( signature_element . to_s ) do |config |
77- config . options = RubySaml ::XML ::BaseDocument ::NOKOGIRI_OPTIONS
78- end
116+ canon_string = signed_info_element . canonicalize ( canon_algorithm )
117+ Base64 . encode64 ( private_key . sign ( hash_algorithm , canon_string ) ) . delete ( "\n " )
118+ end
79119
80- noko_signed_info_element = noko_sig_element . at_xpath ( '//ds:Signature/ds:SignedInfo' , 'ds' => RubySaml ::XML ::Crypto ::DSIG )
81- canon_string = noko_signed_info_element . canonicalize ( RubySaml ::XML ::Crypto . canon_algorithm ( RubySaml ::XML ::Crypto ::C14N ) )
120+ def certificate_value ( certificate )
121+ certificate = OpenSSL ::X509 ::Certificate . new ( certificate ) if certificate . is_a? ( String )
122+ Base64 . encode64 ( certificate . to_der ) . delete ( "\n " )
123+ end
82124
83- signature = compute_signature ( private_key , RubySaml ::XML ::Crypto . hash_algorithm ( signature_method ) . new , canon_string )
84- signature_element . add_element ( 'ds:SignatureValue' ) . text = signature
125+ # TODO: This is a shim method which will be removed when the
126+ # full Nokogiri conversion is complete
127+ def convert_nokogiri_to_rexml ( noko_element , parent_namespaces = Set . new )
128+ rexml_element = REXML ::Element . new ( "#{ "#{ noko_element . namespace . prefix } :" if noko_element . namespace } #{ noko_element . name } " )
85129
86- # add KeyInfo
87- key_info_element = signature_element . add_element ( 'ds:KeyInfo' )
88- x509_element = key_info_element . add_element ( 'ds:X509Data' )
89- x509_cert_element = x509_element . add_element ( 'ds:X509Certificate' )
90- if certificate . is_a? ( String )
91- certificate = OpenSSL ::X509 ::Certificate . new ( certificate )
130+ if noko_element . namespace && !parent_namespaces . include? ( noko_element . namespace )
131+ rexml_element . add_namespace ( noko_element . namespace . prefix , noko_element . namespace . href )
92132 end
93- x509_cert_element . text = Base64 . encode64 ( certificate . to_der ) . gsub ( /\n / , '' )
94133
95- # add the signature
96- issuer_element = elements [ '//saml:Issuer' ]
97- if issuer_element
98- root . insert_after ( issuer_element , signature_element )
99- elsif ( first_child = root . children [ 0 ] )
100- root . insert_before ( first_child , signature_element )
101- else
102- root . add_element ( signature_element )
134+ noko_element . attributes . each do |name , value |
135+ rexml_element . add_attribute ( name , value )
103136 end
104- end
105137
106- private
138+ # Copy text content (if any)
139+ if noko_element . text?
140+ rexml_element . text = noko_element . text
141+ end
107142
108- def compute_signature ( private_key , signature_hash_algorithm , document )
109- Base64 . encode64 ( private_key . sign ( signature_hash_algorithm , document ) ) . gsub ( /\n / , '' )
110- end
143+ # Recursively copy child elements
144+ noko_element . children . each do |child |
145+ if child . element?
146+ rexml_element . add_element ( convert_nokogiri_to_rexml ( child , parent_namespaces << noko_element . namespace ) )
147+ elsif child . text?
148+ rexml_element . add_text ( child . text )
149+ end
150+ end
111151
112- def compute_digest ( document , digest_algorithm )
113- digest = digest_algorithm . digest ( document )
114- Base64 . encode64 ( digest ) . strip
152+ rexml_element
115153 end
116154 end
117155 end
0 commit comments