File Coverage

blib/lib/Net/SAML2/XML/Sig.pm
Criterion Covered Total %
statement 368 584 63.0
branch 92 242 38.0
condition 8 30 26.6
subroutine 40 51 78.4
pod 4 4 100.0
total 512 911 56.2


line stmt bran cond sub pod time code
1             package Net::SAML2::XML::Sig;
2              
3 13     13   98 use strict;
  13         32  
  13         456  
4 13     13   97 use warnings;
  13         26  
  13         449  
5              
6             # ABSTRACT: Net::SAML2::XML::Sig - A toolkit to help sign and verify XML Digital Signatures
7              
8              
9             # use 'our' on v5.6.0
10 13     13   71 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS $DEBUG);
  13         26  
  13         1161  
11              
12             $DEBUG = 0;
13             # Based on XML::Sig VERSION = '0.47';
14             our $VERSION = '0.41';
15              
16 13     13   83 use base qw(Class::Accessor);
  13         26  
  13         8088  
17             Net::SAML2::XML::Sig->mk_accessors(qw(key));
18              
19             # We are exporting functions
20 13     13   25663 use base qw/Exporter/;
  13         33  
  13         1430  
21              
22             # Export list - to allow fine tuning of export table
23             @EXPORT_OK = qw( sign verify );
24              
25              
26 13     13   11641 use Digest::SHA qw(sha1 sha224 sha256 sha384 sha512);
  13         44729  
  13         1404  
27 13     13   122 use XML::LibXML;
  13         31  
  13         142  
28 13     13   8982 use Net::SAML2::XML::Util qw/ no_comments /;
  13         38  
  13         853  
29 13     13   6481 use MIME::Base64;
  13         9235  
  13         766  
30 13     13   100 use Carp;
  13         28  
  13         787  
31              
32              
33 13     13   83 use constant TRANSFORM_ENV_SIG => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature';
  13         27  
  13         943  
34 13     13   83 use constant TRANSFORM_C14N => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
  13         62  
  13         799  
35 13     13   93 use constant TRANSFORM_C14N_COMMENTS => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments';
  13         31  
  13         587  
36 13     13   74 use constant TRANSFORM_C14N_V1_1 => 'http://www.w3.org/TR/2008/REC-xml-c14n11-20080502';
  13         27  
  13         742  
37 13     13   90 use constant TRANSFORM_C14N_V1_1_COMMENTS => 'http://www.w3.org/TR/2008/REC-xml-c14n11-20080502#WithComments';
  13         35  
  13         739  
38 13     13   90 use constant TRANSFORM_EXC_C14N => 'http://www.w3.org/2001/10/xml-exc-c14n#';
  13         24  
  13         661  
39 13     13   407 use constant TRANSFORM_EXC_C14N_COMMENTS => 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
  13         23  
  13         61593  
40              
41       0     sub DESTROY { }
42              
43             $SIG{INT} = sub { die "Interrupted\n"; };
44              
45             $| = 1; # autoflush
46              
47              
48              
49              
50             sub new {
51 6     6 1 6587 my $class = shift;
52 6         11 my $params = shift;
53 6         14 my $self = {};
54 6         21 foreach my $prop ( qw/ key cert cert_text / ) {
55 18 100       64 if ( exists $params->{ $prop } ) {
56 7         17 $self->{ $prop } = $params->{ $prop };
57             }
58             # else {
59             # confess "You need to provide the $prop parameter!";
60             # }
61             }
62 6         19 bless $self, $class;
63 6 100       70 $self->{ 'x509' } = exists $params->{ x509 } ? 1 : 0;
64 6 100       23 if ( exists $params->{ 'key' } ) {
65 3         19 $self->_load_key( $params->{ 'key' } );
66             }
67 6 100       23 if ( exists $params->{ 'cert' } ) {
68 3         17 $self->_load_cert_file( $params->{ 'cert' } );
69             }
70 6 100       35 if ( exists $params->{ 'cert_text' } ) {
71 1         9 $self->_load_cert_text( $params->{ 'cert_text' } );
72             }
73              
74 6 50 33     34 if ( exists $params->{ sig_hash } && grep { $_ eq $params->{ sig_hash } } ('sha224', 'sha256', 'sha384', 'sha512'))
  0         0  
75             {
76 0         0 $self->{ sig_hash } = $params->{ sig_hash };
77             }
78             else {
79 6         20 $self->{ sig_hash } = 'sha1';
80             }
81              
82 6 50 33     26 if ( exists $params->{ digest_hash } && grep { $_ eq $params->{ digest_hash } } ('sha1', 'sha224', 'sha256', 'sha384',, 'sha512'))
  0         0  
83             {
84 0         0 $self->{ digest_hash } = $params->{ digest_hash };
85             }
86             else {
87 6         18 $self->{ digest_hash } = 'sha1';
88             }
89              
90 6 50 66     44 if (defined $self->{ key_type } && $self->{ key_type } eq 'dsa') {
91 0 0 0     0 if ( defined $params->{ sig_hash } && grep { $_ eq $params->{ sig_hash } } ('sha1', 'sha256')) {
  0         0  
92 0         0 $self->{ sig_hash } = $params->{ sig_hash };
93             }
94             else {
95 0         0 $self->{ sig_hash } = 'sha1';
96             }
97             }
98              
99 6 100 66     34 if ( exists $params->{ no_xml_declaration } && $params->{ no_xml_declaration } == 1 ) {
100 4         10 $self->{ no_xml_declaration } = 1;
101             } else {
102 2         6 $self->{ no_xml_declaration } = 0;
103             }
104              
105 6         23 return $self;
106             }
107              
108              
109             sub sign {
110 3     3 1 443 my $self = shift;
111 3         9 my ($xml) = @_;
112              
113 3 50       20 die "You cannot sign XML without a private key." unless $self->key;
114              
115 3         86 local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };
116              
117 3         20 my $dom = no_comments($xml);
118             #my $dom = XML::LibXML->load_xml(
119             # string => $xml,
120             # no_network => 1,
121             # load_ext_dtd => 0,
122             # expand_entities => 0 );
123              
124 3         61 $self->{ parser } = XML::LibXML::XPathContext->new($dom);
125 3         25 $self->{ parser }->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
126 3         15 $self->{ parser }->registerNs('ec', 'http://www.w3.org/2001/10/xml-exc-c14n#');
127 3         13 $self->{ parser }->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
128              
129 3 50       14 print ("Signing XML\n") if $DEBUG;
130              
131 3         15 my @ids_to_sign = $self->_get_ids_to_sign();
132              
133 3         11 foreach (@ids_to_sign) {
134 4         52 my $signid = $_;
135             # Temporarily create the Signature XML from the part
136             # TODO: ths section needs a rewrite to create the xml in
137             # a better way.
138              
139             # Create a Reference xml fragment including digest section
140 4         26 my $digest_xml = $self->_reference_xml( $signid, "REPLACE DIGEST " . $signid );
141              
142             # Create a SignedInfo xml fragment including digest section
143 4         20 my $signed_info = $self->_signedinfo_xml( $digest_xml );
144              
145             # Create a Signature xml fragment including SignedInfo section
146 4         24 my $signature_xml = $self->_signature_xml( $signed_info, 'REPLACE SIGNATURE ' . $signid );
147              
148 4 50       16 print ("Sign ID: $signid\n") if $DEBUG;
149              
150             # Get the XML note to sign base on the ID
151 4         18 my $xml = $self->_get_xml_to_sign($signid);
152              
153             # Set the namespace but do not apply it to the XML
154 4         24 $xml->setNamespace("http://www.w3.org/2000/09/xmldsig#", "dsig", 0);
155              
156             # Canonicalize the XML to http://www.w3.org/2001/10/xml-exc-c14n#
157             # TODO Change the Canonicalization method in the xml fragment from _signedinfo_xml
158             # <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
159             # <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
160 4         95 my $xml_canon = $xml->toStringEC14N();
161              
162 4 50       1509 if(my $ref = Digest::SHA->can($self->{ digest_hash })) {
163 4         14 $self->{digest_method} = $ref;
164             }
165             else {
166 0         0 die("Can't handle $self->{ digest_hash }");
167             }
168              
169             # Calculate the digest of the XML being signed
170 4         105 my $bin_digest = $self->{digest_method}->( $xml_canon );
171 4         19 my $digest = encode_base64( $bin_digest, '' );
172 4 50       15 print (" Digest: $digest\n") if $DEBUG;
173              
174             # Display the ID of the XML being signed for debugging
175 4         11 my $reference = $signid; #$self->{parser}->findvalue('//@ID', $xml);
176 4 50       12 print (" Reference URI: $reference\n") if $DEBUG;
177              
178             # Add the Signature to the xml being signed
179 4         25 $xml->appendWellBalancedChunk($signature_xml, 'UTF-8');
180              
181             # Canonicalize the SignedInfo to http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments
182             # TODO Change the Canonicalization method in the xml fragment from _signedinfo_xml
183              
184 4         1079 my ($signature_node) = $xml->findnodes(
185             './dsig:Signature', $xml);
186 4         234 my ($signed_info_node) = $xml->findnodes(
187             './dsig:Signature/dsig:SignedInfo',$xml);
188              
189             # Add the digest value to the Signed info
190 4         116 my ($digest_value_node) = $xml->findnodes(
191             './dsig:Signature/dsig:SignedInfo/dsig:Reference/dsig:DigestValue', $signature_node);
192 4         130 $digest_value_node->removeChildNodes();
193 4         21 $digest_value_node->appendText($digest);
194              
195             # At this point the SignedInfo includes the information
196             # to allow us to use the _canonicalize_xml with the $signature_node
197 4         16 my $signed_info_canon = $self->_canonicalize_xml($signed_info_node, $signature_node);
198              
199             # Calculate the signature of the Canonical Form of SignedInfo
200 4         126 my $signature;
201 4 50       21 if ($self->{key_type} eq 'dsa') {
    50          
202 0 0       0 print (" Signing SignedInfo using DSA key type\n") if $DEBUG;
203 0 0       0 if(my $ref = Digest::SHA->can($self->{ sig_hash })) {
204 0         0 $self->{sig_method} = $ref;
205             }
206             else {
207 0         0 die("Can't handle $self->{ sig_hash }");
208             }
209              
210             # DSA 1024-bit only permits the signing of 20 bytes or less, hence the sha1
211             # DSA 2048-bit only permits the signing sha256
212 0         0 my $bin_signature = $self->{key_obj}->do_sign( $self->{ sig_method }($signed_info_canon) );
213              
214             # https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/#sec-SignatureAlg
215             # The output of the DSA algorithm consists of a pair of integers
216             # The signature value consists of the base64 encoding of the
217             # concatenation of r and s in that order ($r . $s)
218 0         0 my $r = $bin_signature->get_r;
219 0         0 my $s = $bin_signature->get_s;
220              
221 0         0 my $sig_size = ($self->{key_obj}->get_sig_size - 8) * 8;
222 0         0 my $rs = _zero_fill_buffer($sig_size);
223 0         0 _concat_dsa_sig_r_s(\$rs, $r, $s, $sig_size);
224              
225 0         0 $signature = encode_base64( $rs, "\n" );
226             } elsif ($self->{key_type} eq 'ecdsa') {
227 0 0       0 print (" Signing SignedInfo using ECDSA key type\n") if $DEBUG;
228              
229             my $bin_signature = $self->{key_obj}->sign_message_rfc7518(
230             $signed_info_canon, uc($self->{sig_hash})
231 0         0 );
232             # The output of the ECDSA algorithm consists of a pair of integers
233             # The signature value consists of the base64 encoding of the
234             # concatenation of r and s in that order ($r . $s). In this
235             # case sign_message_rfc7518 produces that
236              
237 0         0 $signature = encode_base64( $bin_signature, "\n" );
238             } else {
239 4 50       14 print (" Signing SignedInfo using RSA key type\n") if $DEBUG;
240 4         18 my $sig_hash = 'use_' . $self->{ sig_hash } . '_hash';
241 4         24 $self->{key_obj}->$sig_hash;
242 4         5885 my $bin_signature = $self->{key_obj}->sign( $signed_info_canon );
243 4         31 $signature = encode_base64( $bin_signature, "\n" );
244             }
245              
246             # Add the Signature to the SignatureValue
247 4         22 my ($signature_value_node) = $xml->findnodes(
248             './dsig:Signature/dsig:SignatureValue', $signature_node);
249 4         166 $signature_value_node->removeChildNodes();
250 4         24 $signature_value_node->appendText($signature);
251              
252 4 50       22 print ("\n\n\n SignatureValue:\n" . $signature_value_node . "\n\n\n") if $DEBUG;
253             }
254              
255 3         146 return $dom->toString;
256             }
257              
258              
259             sub verify {
260 5     5 1 430 my $self = shift;
261 5         13 delete $self->{signer_cert};
262 5         19 my ($xml) = @_;
263              
264 5         26 my $dom = no_comments($xml);
265             #my $dom = XML::LibXML->load_xml(
266             # string => $xml,
267             # no_network => 1,
268             # load_ext_dtd => 0,
269             # expand_entities => 0 );
270              
271 5         109 $self->{ parser } = XML::LibXML::XPathContext->new($dom);
272 5         60 $self->{ parser }->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
273 5         25 $self->{ parser }->registerNs('ec', 'http://www.w3.org/2001/10/xml-exc-c14n#');
274 5         21 $self->{ parser }->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
275 5         25 $self->{ parser }->registerNs('ecdsa', 'http://www.w3.org/2001/04/xmldsig-more#');
276              
277 5         25 my $signature_nodeset = $self->{ parser }->findnodes('//dsig:Signature');
278              
279 5         273 my $numsigs = $signature_nodeset->size();
280 5 50       77 print ("NodeSet Size: $numsigs\n") if $DEBUG;
281              
282             # Loop through each Signature in the document checking each
283 5         14 my $i;
284 5         22 while (my $signature_node = $signature_nodeset->shift()) {
285 6         165 $i++;
286 6 50       22 print ("\nSignature $i\n") if $DEBUG;
287              
288             # Get SignedInfo Reference ID
289             my $reference = $self->{ parser }->findvalue(
290 6         36 'dsig:SignedInfo/dsig:Reference/@URI', $signature_node);
291 6         725 $reference =~ s/#//g;
292              
293 6 50       31 print (" Reference URI: $reference\n") if $DEBUG;
294              
295             # The reference ID must point to something in the document
296             # if not disregard it and look for another signature
297             # TODO check to ensure that if there is only a single reference
298             # like this it won't accidentally validate
299 6 50       39 if (! $self->{ parser }->findvalue('//*[@ID=\''. $reference . '\']')) {
300 0 0       0 print (" Signature reference $reference is not signing anything in this xml\n") if $DEBUG;
301 0 0       0 if ($numsigs <= 1) {
302 0         0 return 0;
303             }
304             else {
305 0         0 next;
306             }
307             }
308              
309             # Get SignedInfo DigestMethod Algorithim
310             my $digest_method = $self->{ parser }->findvalue(
311 6         735 'dsig:SignedInfo/dsig:Reference/dsig:DigestMethod/@Algorithm', $signature_node);
312 6         573 $digest_method =~ s/^.*[#]//;
313 6 50       28 print (" Digest Method: $digest_method\n") if $DEBUG;
314              
315             # Get the DigestValue used to verify Canonical XML
316             # Note that the digest may have embedded newlines in the XML
317             # Decode the base64 and encode it with no newlines
318             my $refdigest = encode_base64(decode_base64(_trim($self->{ parser }->findvalue(
319 6         25 'dsig:SignedInfo/dsig:Reference/dsig:DigestValue', $signature_node))), "");
320 6 50       26 print (" Digest Value: $refdigest\n") if $DEBUG;
321              
322             # Get the SignatureValue used to verify the SignedInfo
323 6         104 my $signature = _trim($self->{ parser }->findvalue('dsig:SignatureValue', $signature_node));
324 6 50       22 print (" Signature: $signature\n") if $DEBUG;
325              
326             # Get SignatureMethod Algorithim
327             my $signature_method = $self->{ parser }->findvalue(
328 6         100 'dsig:SignedInfo/dsig:SignatureMethod/@Algorithm', $signature_node);
329 6         434 $signature_method =~ s/^.*[#]//;
330 6         67 $signature_method =~ s/^rsa-//;
331 6         23 $signature_method =~ s/^dsa-//;
332 6         12 $signature_method =~ s/^ecdsa-//;
333              
334 6         18 $self->{ sig_hash } = $signature_method;
335 6 50       21 print (" SignatureMethod: $signature_method\n") if $DEBUG;
336              
337             # Get the SignedInfo and obtain its Canonical form
338 6         30 my ($signed_info) = $self->{ parser }->findnodes('dsig:SignedInfo', $signature_node);
339 6         265 my $signed_info_canon = $self->_canonicalize_xml($signed_info, $signature_node);
340              
341 6 50       115 print "$signed_info_canon\n" if $DEBUG;
342              
343 6 50       88 if(my $ref = Digest::SHA->can($signature_method)) {
344 6         24 $self->{sig_method} = $ref;
345             }
346             else {
347 0         0 die("Can't handle $signature_method");
348             }
349              
350 6 50       36 if(my $ref = Digest::SHA->can($digest_method)) {
351 6         18 $self->{digest_method} = $ref;
352             }
353             else {
354 0         0 die("Can't handle $digest_method");
355             }
356              
357             # If a cert was provided to Net::SAML2::XML::Sig->new() use it to
358             # verify the SignedInfo signature
359 6 100       23 if (defined $self->{cert_obj}) {
360             # use the provided cert to verify
361 3 50       20 unless ($self->_verify_x509_cert($self->{cert_obj},$signed_info_canon,$signature)) {
362 0         0 print STDERR "not verified by x509\n";
363 0         0 return 0;
364             }
365             }
366             # Extract the XML provided certificate and use it to
367             # verify the SignedInfo signature
368             else {
369             # extract the certficate or key from the document
370 3         22 my %verify_dispatch = (
371             'X509Data' => '_verify_x509',
372             'RSAKeyValue' => '_verify_rsa',
373             'DSAKeyValue' => '_verify_dsa',
374             'ECDSAKeyValue' => '_verify_ecdsa',
375             );
376 3         6 my $keyinfo_nodeset;
377 3         11 foreach my $key_info_sig_type ( qw/X509Data RSAKeyValue DSAKeyValue ECDSAKeyValue/ ) {
378 3 50       11 if ( $key_info_sig_type eq 'X509Data' ) {
379             $keyinfo_nodeset = $self->{ parser }->find(
380 3         17 "dsig:KeyInfo/dsig:$key_info_sig_type", $signature_node);
381             #print (" keyinfo_nodeset X509Data: $keyinfo_nodeset\n") if $DEBUG;
382             } else {
383             $keyinfo_nodeset = $self->{ parser }->find(
384 0         0 "dsig:KeyInfo/dsig:KeyValue/dsig:$key_info_sig_type", $signature_node);
385             #print (" keyinfo_nodeset [DR]SAKeyValue: $keyinfo_nodeset\n") if $DEBUG;
386             }
387 3 50       152 if ( $keyinfo_nodeset->size ) {
388 3         21 my $verify_method = $verify_dispatch{$key_info_sig_type};
389 3 50       10 print (" Verify Method: $verify_method\n") if $DEBUG;
390 3 50       12 if ( ! $self->$verify_method($keyinfo_nodeset->get_node(0),
391             $signed_info_canon, $signature) ) {
392 0 0       0 print ("keyinfo_nodeset->get_node: " . $keyinfo_nodeset->get_node(0) . "\n") if $DEBUG;
393 0         0 print STDERR "Failed to verify using $verify_method\n";
394 0         0 return 0;
395             } else {
396 3 50       12 print ("Success Verifying\n") if $DEBUG;
397             }
398 3         20 last;
399             }
400             }
401 3 50 33     19 die "Unrecognized key type or no KeyInfo in document" unless (
402             $keyinfo_nodeset && $keyinfo_nodeset->size > 0);
403             }
404              
405             # Signature of SignedInfo was verified above now obtain the
406             # Canonical form of the XML and verify the DigestValue of the XML
407              
408             # Remove the Signature from the signed XML
409 6         302 my $signed_xml = $self->_get_signed_xml( $signature_node );
410 6         99 $signed_xml->removeChild( $signature_node );
411              
412             # Obtain the Canonical form of the XML
413 6         20 my $canonical = $self->_transform($signed_xml, $signature_node);
414              
415             # Add the $signature_node back to the $signed_xml to allow other
416             # signatures to be validated if they exist
417 6         205 $signed_xml->addChild( $signature_node );
418              
419             # Obtain the DigestValue of the Canonical XML
420 6         17 my $digest = $self->{digest_method}->($canonical);
421              
422 6 50       197 print ( " Reference Digest: " . _trim($refdigest) ."\n") if $DEBUG;
423              
424 6 50       21 print ( " Calculated Digest: ". _trim(encode_base64($digest, '')) ."\n") if $DEBUG;
425              
426             # Return 0 - fail verification on the first XML signature that fails
427 6 50       37 return 0 unless ($refdigest eq _trim(encode_base64($digest, '')));
428              
429 6 50       30 print ( "Signature $i Valid\n") if $DEBUG;
430             }
431              
432 5         189 return 1;
433             }
434              
435              
436             sub signer_cert {
437 3     3 1 6 my $self = shift;
438 3         24 return $self->{signer_cert};
439             }
440              
441             ##
442             ## _get_ids_to_sign()
443             ##
444             ## Arguments:
445             ##
446             ## Returns: array Value of ID attributes from XML
447             ##
448             ## Finds all the values of the ID attributes in the XML
449             ## and return them in reverse order found. Reverse order
450             ## assumes that the Signatures should be performed on lower
451             ## Nodes first.
452             ##
453             sub _get_ids_to_sign {
454 3     3   8 my $self = shift;
455 3         16 my @id = $self->{parser}->findnodes('//@ID');
456 3         138 my @ids;
457 3         11 foreach (@id) {
458 4         9 my $i = $_;
459 4         144 $_ =~ m/^.*\"(.*)\".*$/;
460 4         106 $i = $1;
461             #//*[@ID='identifier_1']
462 4 50       17 die "You cannot sign an XML document without identifying the element to sign with an ID attribute" unless $i;
463 4         15 unshift @ids, $i;
464             }
465 3         20 return @ids;
466              
467              
468             }
469              
470             ##
471             ## _get_xml_to_sign()
472             ##
473             ## Arguments:
474             ## $id: string ID of the Node for the XML to retrieve
475             ##
476             ## Returns: XML NodeSet to sign
477             ##
478             ## Find the XML node with the ID = $id and return the
479             ## XML NodeSet
480             ##
481             sub _get_xml_to_sign {
482 4     4   6 my $self = shift;
483 4         8 my $id = shift;
484 4 50       14 die "You cannot sign an XML document without identifying the element to sign with an ID attribute" unless $id;
485              
486 4         14 my $xpath = "//*[\@ID='$id']";
487 4         21 my ($node) = $self->_get_node( $xpath );
488 4         11 return $node;
489             }
490              
491             ##
492             ## _get_signed_xml($context)
493             ##
494             ## Arguments:
495             ## $context: string XML NodeSet used as context
496             ##
497             ## Returns: XML NodeSet for with ID equal to the URI
498             ##
499             ## Find the XML node with the ID = $URI and return the
500             ## XML NodeSet
501             ##
502             sub _get_signed_xml {
503 6     6   18 my $self = shift;
504 6         15 my ($context) = @_;
505              
506 6         33 my $id = $self->{parser}->findvalue('./dsig:SignedInfo/dsig:Reference/@URI', $context);
507 6         592 $id =~ s/^#//;
508 6 50       29 print (" Signed XML id: $id\n") if $DEBUG;
509              
510 6         19 $self->{'sign_id'} = $id;
511 6         24 my $xpath = "//*[\@ID='$id']";
512 6         29 return $self->_get_node( $xpath, $context );
513             }
514              
515             ##
516             ## _transform($xml, $context)
517             ##
518             ## Arguments:
519             ## $xml: string XML NodeSet
520             ## $context: string XML Context
521             ##
522             ## Returns: string Transformed XML
523             ##
524             ## Canonicalizes/Transforms xml based on the Transforms
525             ## from the SignedInfo.
526             ##
527             sub _transform {
528 6     6   94 my $self = shift;
529 6         19 my ($xml, $context) = @_;
530              
531 6         27 $context->setNamespace( 'http://www.w3.org/2000/09/xmldsig#', 'dsig' );
532             my $transforms = $self->{parser}->find(
533 6         148 'dsig:SignedInfo/dsig:Reference/dsig:Transforms/dsig:Transform',
534             $context
535             );
536              
537 6 50       314 print "_transform\n" if $DEBUG;
538 6         26 foreach my $node ($transforms->get_nodelist) {
539 12         73 my $alg = $node->getAttribute('Algorithm');
540              
541 12 50       141 print " Algorithm: $alg\n" if $DEBUG;
542 12 100       67 if ($alg eq TRANSFORM_ENV_SIG) {
    50          
    50          
    50          
    0          
543             # TODO the xml being passed here currently has the
544             # Signature removed. May be better to do it all here
545 6         17 next;
546             }
547             elsif ($alg eq TRANSFORM_C14N) {
548 0 0       0 print " toStringC14N" if $DEBUG;
549 0         0 $xml = $xml->toStringC14N();
550             }
551             elsif ($alg eq TRANSFORM_C14N_COMMENTS) {
552 0 0       0 print " toStringC14N(1)" if $DEBUG;
553 0         0 $xml = $xml->toStringC14N(1);
554             }
555             elsif ($alg eq TRANSFORM_EXC_C14N) {
556 6         25 my @prefixlist = $self->_find_prefixlist($node);
557 6 50       21 print " toStringEC14N(0, '', @prefixlist)\n" if $DEBUG;
558 6         30 $xml = $xml->toStringEC14N(0, '', \@prefixlist);
559             }
560             elsif ($alg eq TRANSFORM_EXC_C14N_COMMENTS) {
561 0         0 my @prefixlist = $self->_find_prefixlist($node);
562 0 0       0 print " toStringEC14N(1, '', @prefixlist)\n" if $DEBUG;
563 0         0 $xml = $xml->toStringEC14N(1, '', \@prefixlist);
564             }
565             else {
566 0         0 die "Unsupported transform: $alg";
567             }
568             }
569 6         2096 return $xml;
570             }
571              
572             ##
573             ## _find_prefixlist($node)
574             ##
575             ## Arguments:
576             ## $node: string XML NodeSet
577             ##
578             ## Returns: ARRAY of prefix lists
579             ##
580             ## Generate an array of prefix lists defined in InclusiveNamespaces
581             ##
582             sub _find_prefixlist {
583 6     6   14 my $self = shift;
584 6         27 my ($node) = @_;
585 6         34 my @children = $node->getChildrenByLocalName('InclusiveNamespaces');
586              
587 6         92 my $prefixlist = '';
588 6         21 foreach my $child (@children) {
589 0 0       0 if ($child) {
590 0         0 $prefixlist .= $child->getAttribute('PrefixList');
591             }
592 0         0 $prefixlist .= ' ';
593             }
594 6         28 return split / /, $prefixlist;
595             }
596              
597             ##
598             ## _verify_rsa($context,$canonical,$sig)
599             ##
600             ## Arguments:
601             ## $context: string XML Context to use
602             ## $canonical: string Canonical XML to verify
603             ## $sig: string Base64 encode of RSA Signature
604             ##
605             ## Returns: integer (1 True, 0 False) if signature is valid
606             ##
607             ## Verify the RSA signature of Canonical XML
608             ##
609             sub _verify_rsa {
610 0     0   0 my $self = shift;
611 0         0 my ($context,$canonical,$sig) = @_;
612              
613             # Generate Public Key from XML
614 0         0 my $mod = _trim($self->{parser}->findvalue('dsig:Modulus', $context));
615 0         0 my $modBin = decode_base64( $mod );
616 0         0 my $exp = _trim($self->{parser}->findvalue('dsig:Exponent', $context));
617 0         0 my $expBin = decode_base64( $exp );
618 0         0 my $n = Crypt::OpenSSL::Bignum->new_from_bin($modBin);
619 0         0 my $e = Crypt::OpenSSL::Bignum->new_from_bin($expBin);
620 0         0 my $rsa_pub = Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e );
621              
622             # Decode signature and verify
623 0         0 my $sig_hash = 'use_' . $self->{ sig_hash } . '_hash';
624 0         0 $rsa_pub->$sig_hash;
625 0         0 my $bin_signature = decode_base64($sig);
626 0 0       0 return 1 if ($rsa_pub->verify( $canonical, $bin_signature ));
627 0         0 return 0;
628             }
629              
630             ##
631             ## _clean_x509($cert)
632             ##
633             ## Arguments:
634             ## $cert: string Certificate in base64 from XML
635             ##
636             ## Returns: string Certificate in Valid PEM format
637             ##
638             ## Reformats Certifcate string into PEM format 64 characters
639             ## with proper header and footer
640             ##
641             sub _clean_x509 {
642 3     3   47 my $self = shift;
643 3         7 my ($cert) = @_;
644              
645 3 50       11 $cert = $cert->value() if(ref $cert);
646 3         10 chomp($cert);
647              
648             # rewrap the base64 data from the certificate; it may not be
649             # wrapped at 64 characters as PEM requires
650 3         63 $cert =~ s/\n//g;
651              
652 3         8 my @lines;
653 3         40 while (length $cert > 64) {
654 78         565 push @lines, substr $cert, 0, 64, '';
655             }
656 3         9 push @lines, $cert;
657              
658 3         24 $cert = join "\n", @lines;
659              
660 3         17 $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "\n-----END CERTIFICATE-----\n";
661 3         16 return $cert;
662             }
663              
664             ##
665             ## _verify_x509($context,$canonical,$sig)
666             ##
667             ## Arguments:
668             ## $context: string XML Context to use
669             ## $canonical: string Canonical XML to verify
670             ## $sig: string Base64 encode of RSA Signature
671             ##
672             ## Returns: integer (1 True, 0 False) if signature is valid
673             ##
674             ## Verify the RSA signature of Canonical XML using an X509
675             ##
676             sub _verify_x509 {
677 3     3   34 my $self = shift;
678 3         8 my ($context,$canonical,$sig) = @_;
679              
680 3         7 eval {
681 3         25 require Crypt::OpenSSL::X509;
682             };
683 3 50       9 confess "Crypt::OpenSSL::X509 needs to be installed so that we can handle X509 certificates" if $@;
684              
685             # Generate Public Key from XML
686 3         13 my $certificate = _trim($self->{parser}->findvalue('dsig:X509Certificate', $context));
687              
688             # This is added because the X509 parser requires it for self-identification
689 3         13 $certificate = $self->_clean_x509($certificate);
690              
691 3         315 my $cert = Crypt::OpenSSL::X509->new_from_string($certificate);
692              
693 3         21 return $self->_verify_x509_cert($cert, $canonical, $sig);
694             }
695              
696             ##
697             ## _verify_x509_cert($cert,$canonical,$sig)
698             ##
699             ## Arguments:
700             ## $cert: string X509 Certificate
701             ## $canonical: string Canonical XML to verify
702             ## $sig: string Base64 encode of [EC|R]SA Signature
703             ##
704             ## Returns: integer (1 True, 0 False) if signature is valid
705             ##
706             ## Verify the X509 signature of Canonical XML
707             ##
708             sub _verify_x509_cert {
709 6     6   15 my $self = shift;
710 6         18 my ($cert, $canonical, $sig) = @_;
711              
712             # Decode signature and verify
713 6         33 my $bin_signature = decode_base64($sig);
714              
715 6 50       124 if ($cert->key_alg_name eq 'id-ecPublicKey') {
    50          
716 0 0       0 eval {require Crypt::PK::ECC; CryptX->VERSION('0.036'); 1}
  0         0  
  0         0  
  0         0  
717             or confess "Crypt::PK::ECC 0.036+ needs to be installed so
718             that we can handle ECDSA signatures";
719 0         0 my $ecdsa_pub = Crypt::PK::ECC->new(\$cert->pubkey);
720              
721 0         0 my $ecdsa_hash = $self->{rsa_hash};
722              
723             # Signature is stored as the concatenation of r and s.
724             # verify_message_rfc7518 expects that format
725 0 0       0 if ($ecdsa_pub->verify_message_rfc7518( $bin_signature, $canonical, uc($self->{sig_hash}) )) {
726 0         0 $self->{signer_cert} = $cert;
727 0         0 return 1;
728             }
729             }
730             elsif ($cert->key_alg_name eq 'dsaEncryption') {
731 0         0 eval {
732 0         0 require Crypt::OpenSSL::DSA;
733             };
734 0 0       0 confess "Crypt::OpenSSL::DSA needs to be installed so
735             that we can handle DSA X509 certificates" if $@;
736              
737 0         0 my $dsa_pub = Crypt::OpenSSL::DSA->read_pub_key_str( $cert->pubkey );
738 0         0 my $sig_size = ($dsa_pub->get_sig_size - 8)/2;
739             #my ($r, $s) = unpack('a20a20', $bin_signature);
740 0         0 my $unpk = "a" . $sig_size . "a" . $sig_size;
741 0         0 my ($r, $s) = unpack($unpk, $bin_signature);
742              
743             # Create a new Signature Object from r and s
744 0         0 my $sigobj = Crypt::OpenSSL::DSA::Signature->new();
745 0         0 $sigobj->set_r($r);
746 0         0 $sigobj->set_s($s);
747              
748 0 0       0 if ($dsa_pub->do_verify($self->{sig_method}->($canonical), $sigobj)) {
749 0         0 $self->{signer_cert} = $cert;
750 0         0 return 1;
751             }
752             }
753             else {
754 6         15 eval {
755 6         49 require Crypt::OpenSSL::RSA;
756             };
757 6 50       22 confess "Crypt::OpenSSL::RSA needs to be installed so
758             that we can handle X509 certificates" if $@;
759              
760 6         403 my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
761              
762 6         3064 my $sig_hash = 'use_' . $self->{sig_hash} . '_hash';
763 6         43 $rsa_pub->$sig_hash();
764             # If successful verify, store the signer's cert for validation
765 6 50       475 if ($rsa_pub->verify( $canonical, $bin_signature )) {
766 6         36 $self->{signer_cert} = $cert;
767 6         61 return 1;
768             }
769             }
770              
771 0         0 return 0;
772             }
773              
774             ##
775             ## _zero_fill_buffer($bits)
776             ##
777             ## Arguments:
778             ## $bits: number of bits to set to zero
779             ##
780             ## Returns: Zero filled bit buffer of size $bits
781             ##
782             ## Create a buffer with all bits set to 0
783             ##
784             sub _zero_fill_buffer {
785 0     0   0 my $bits = shift;
786             # set all bit to zero
787 0         0 my $v = '';
788 0         0 for (my $i = 0; $i < $bits; $i++) {
789 0         0 vec($v, $i, 1) = 0;
790             }
791 0         0 return $v;
792             }
793              
794             ##
795             ## _concat_dsa_sig_r_s(\$buffer,$r,$s)
796             ##
797             ## Arguments:
798             ## $buffer: Zero Filled bit buffer
799             ## $r: octet stream
800             ## $s: octet stream
801             ##
802             ## Combine r and s components of DSA signature
803             ##
804             sub _concat_dsa_sig_r_s {
805              
806 0     0   0 my ($buffer, $r, $s, $sig_size) = @_;
807 0         0 my $bits_r = (length($r)*8)-1;
808 0         0 my $bits_s = (length($s)*8)-1;
809              
810 0         0 my $halfsize = $sig_size / 2;
811              
812             # Place $s right justified in $v starting at bit 319
813 0         0 for (my $i = $bits_s; $i >=0; $i--) {
814 0         0 vec($$buffer, $halfsize + $i + (($halfsize -1) - $bits_s) , 1) = vec($s, $i, 1);
815             }
816              
817             # Place $r right justified in $v starting at bit 159
818 0         0 for (my $i = $bits_r; $i >= 0 ; $i--) {
819 0         0 vec($$buffer, $i + (($halfsize -1) - $bits_r) , 1) = vec($r, $i, 1);
820             }
821              
822             }
823              
824             ##
825             ## _verify_dsa($context,$canonical,$sig)
826             ##
827             ## Arguments:
828             ## $context: string XML Context to use
829             ## $canonical: string Canonical XML to verify
830             ## $sig: string Base64 encode 40 byte string of r and s
831             ##
832             ## Returns: integer (1 True, 0 False) if signature is valid
833             ##
834             ## Verify the DSA signature of Canonical XML
835             ##
836             sub _verify_dsa {
837 0     0   0 my $self = shift;
838 0         0 my ($context,$canonical,$sig) = @_;
839              
840 0         0 eval {
841 0         0 require Crypt::OpenSSL::DSA;
842             };
843 0 0       0 confess "Crypt::OpenSSL::DSA needs to be installed so
844             that we can handle DSA signatures" if $@;
845              
846             # Generate Public Key from XML
847 0         0 my $p = decode_base64(_trim($self->{parser}->findvalue('dsig:P', $context)));
848 0         0 my $q = decode_base64(_trim($self->{parser}->findvalue('dsig:Q', $context)));
849 0         0 my $g = decode_base64(_trim($self->{parser}->findvalue('dsig:G', $context)));
850 0         0 my $y = decode_base64(_trim($self->{parser}->findvalue('dsig:Y', $context)));
851 0         0 my $dsa_pub = Crypt::OpenSSL::DSA->new();
852 0         0 $dsa_pub->set_p($p);
853 0         0 $dsa_pub->set_q($q);
854 0         0 $dsa_pub->set_g($g);
855 0         0 $dsa_pub->set_pub_key($y);
856              
857             # Decode signature and verify
858 0         0 my $bin_signature = decode_base64($sig);
859              
860             # https://www.w3.org/TR/2002/REC-xmldsig-core-20020212/#sec-SignatureAlg
861             # The output of the DSA algorithm consists of a pair of integers
862             # The signature value consists of the base64 encoding of the
863             # concatenation of r and s in that order ($r . $s)
864             # Binary Signature is stored as a concatenation of r and s
865 0         0 my $sig_size = ($dsa_pub->get_sig_size - 8)/2;
866 0         0 my $unpk = "a" . $sig_size . "a" . $sig_size;
867 0         0 my ($r, $s) = unpack($unpk, $bin_signature);
868              
869             # Create a new Signature Object from r and s
870 0         0 my $sigobj = Crypt::OpenSSL::DSA::Signature->new();
871 0         0 $sigobj->set_r($r);
872 0         0 $sigobj->set_s($s);
873              
874             # DSA signatures are limited to a message body of 20 characters, so a sha1 digest is taken
875 0 0       0 return 1 if ($dsa_pub->do_verify( $self->{sig_method}->($canonical), $sigobj ));
876 0         0 return 0;
877             }
878              
879             ##
880             ## _verify_ecdsa($context,$canonical,$sig)
881             ##
882             ## Arguments:
883             ## $context: string XML Context to use
884             ## $canonical: string Canonical XML to verify
885             ## $sig: string Base64 encoded
886             ##
887             ## Returns: integer (1 True, 0 False) if signature is valid
888             ##
889             ## Verify the ECDSA signature of Canonical XML
890             ##
891             sub _verify_ecdsa {
892 0     0   0 my $self = shift;
893 0         0 my ($context,$canonical,$sig) = @_;
894              
895 0 0       0 eval {require Crypt::PK::ECC; CryptX->VERSION('0.036'); 1}
  0         0  
  0         0  
  0         0  
896             or confess "Crypt::PK::ECC 0.036+ needs to be installed so
897             that we can handle ECDSA signatures";
898             # Generate Public Key from XML
899 0         0 my $oid = _trim($self->{parser}->findvalue('//dsig:NamedCurve/@URN', $context));
900              
901 13     13   152 use URI ();
  13         39  
  13         35668  
902 0         0 my $u1 = URI->new($oid);
903 0         0 $oid = $u1->nss;
904              
905 0         0 my %curve_name = (
906             '1.2.840.10045.3.1.1' => 'secp192r1',
907             '1.3.132.0.33' => 'secp224r1',
908             '1.2.840.10045.3.1.7' => 'secp256r1',
909             '1.3.132.0.34' => 'secp384r1',
910             '1.3.132.0.35' => 'secp521r1',
911             '1.3.36.3.3.2.8.1.1.1' => 'brainpoolP160r1',
912             '1.3.36.3.3.2.8.1.1.3' => 'brainpoolP192r1',
913             '1.3.36.3.3.2.8.1.1.5' => 'brainpoolP224r1',
914             '1.3.36.3.3.2.8.1.1.7' => 'brainpoolP256r1',
915             '1.3.36.3.3.2.8.1.1.9' => 'brainpoolP320r1',
916             '1.3.36.3.3.2.8.1.1.11' => 'brainpoolP384r1',
917             '1.3.36.3.3.2.8.1.1.13' => 'brainpoolP512r1',
918             );
919              
920 0         0 my $x = $self->{parser}->findvalue('//dsig:PublicKey/dsig:X/@Value', $context);
921 0         0 my $y = $self->{parser}->findvalue('//dsig:PublicKey/dsig:Y/@Value', $context);
922              
923 0         0 my $ecdsa_pub = Crypt::PK::ECC->new();
924              
925             $ecdsa_pub->import_key({
926             kty => "EC",
927 0         0 curve_name => $curve_name{ $oid },
928             pub_x => $x,
929             pub_y => $y,
930             });
931              
932 0         0 my $bin_signature = decode_base64($sig);
933              
934             # verify_message_rfc7518 is used to verify signature stored as a
935             # concatenation of integers r and s
936             return 1 if ($ecdsa_pub->verify_message_rfc7518(
937             $bin_signature,
938             $canonical,
939 0 0       0 uc($self->{sig_hash}))
940             );
941 0         0 return 0;
942             }
943              
944             ##
945             ## _get_node($xpath, context)
946             ##
947             ## Arguments:
948             ## $xpath: string XML XPath to use
949             ## $context: string XML context
950             ##
951             ## Returns: string XML NodeSet
952             ##
953             ## Return a NodeSet based on the xpath string
954             ##
955             sub _get_node {
956 10     10   21 my $self = shift;
957 10         26 my ($xpath, $context) = @_;
958 10         19 my $nodeset;
959 10 100       31 if ($context) {
960 6         51 $nodeset = $self->{parser}->find($xpath, $context);
961             } else {
962 4         21 $nodeset = $self->{parser}->find($xpath);
963             }
964 10         825 foreach my $node ($nodeset->get_nodelist) {
965 10         110 return $node;
966             }
967             }
968              
969             # TODO remove unused?
970             sub _get_node_as_text {
971 0     0   0 my $self = shift;
972 0         0 my ($xpath, $context) = @_;
973 0         0 my $node = $self->_get_node($xpath, $context);
974 0 0       0 if ($node) {
975 0         0 return $node->toString;
976             } else {
977 0         0 return '';
978             }
979             }
980              
981             # TODO remove unused?
982             sub _transform_env_sig {
983 0     0   0 my $self = shift;
984 0         0 my ($str) = @_;
985 0         0 my $prefix = '';
986 0 0 0     0 if (defined $self->{dsig_prefix} && length $self->{dsig_prefix}) {
987 0         0 $prefix = $self->{dsig_prefix} . ':';
988             }
989              
990             # This removes the first Signature tag from the XML - even if there is another XML tree with another Signature inside and that comes first.
991             # TODO: Remove the outermost Signature only.
992              
993 0         0 $str =~ s/(<${prefix}Signature(.*?)>(.*?)\<\/${prefix}Signature>)//is;
994              
995 0         0 return $str;
996             }
997              
998             ##
999             ## _trim($string)
1000             ##
1001             ## Arguments:
1002             ## $string: string String to remove whitespace
1003             ##
1004             ## Returns: string Trimmed String
1005             ##
1006             ## Trim the whitespace from the begining and end of the string
1007             ##
1008             sub _trim {
1009 25     25   1112 my $string = shift;
1010 25         146 $string =~ s/^\s+//;
1011 25         248 $string =~ s/\s+$//;
1012 25         123 return $string;
1013             }
1014              
1015             ##
1016             ## _load_ecdsa_key($key_text)
1017             ##
1018             ## Arguments:
1019             ## $key_text: string ECDSA Private Key as String
1020             ##
1021             ## Returns: nothing
1022             ##
1023             ## Populate:
1024             ## self->{KeyInfo}
1025             ## self->{key_obj}
1026             ## self->{key_type}
1027             ##
1028             sub _load_ecdsa_key {
1029 0     0   0 my $self = shift;
1030 0         0 my $key_text = shift;
1031              
1032 0 0       0 eval {require Crypt::PK::ECC; CryptX->VERSION('0.036'); 1}
  0         0  
  0         0  
  0         0  
1033             or confess "Crypt::PK::ECC 0.036+ needs to be installed so
1034             that we can handle ECDSA signatures";
1035              
1036 0         0 my $ecdsa_key = Crypt::PK::ECC->new('t/ecdsa.private.pem');
1037              
1038 0 0       0 if ( $ecdsa_key ) {
1039 0         0 $self->{ key_obj } = $ecdsa_key;
1040              
1041 0         0 my $key_hash = $ecdsa_key->key2hash;
1042              
1043 0         0 my $oid = $key_hash->{ curve_oid };
1044 0         0 my $x = $key_hash->{ pub_x };
1045 0         0 my $y = $key_hash->{ pub_y };
1046              
1047 0         0 $self->{KeyInfo} = "<dsig:KeyInfo>
1048             <dsig:KeyValue>
1049             <dsig:ECDSAKeyValue>
1050             <dsig:DomainParameters>
1051             <dsig:NamedCurve URN=\"urn:oid:$oid\" />
1052             </dsig:DomainParameters>
1053             <dsig:PublicKey>
1054             <dsig:X Value=\"$x\" />
1055             <dsig:Y Value=\"$y\" />
1056             </dsig:PublicKey>
1057             </dsig:ECDSAKeyValue>
1058             </dsig:KeyValue>
1059             </dsig:KeyInfo>";
1060 0         0 $self->{key_type} = 'ecdsa';
1061             }
1062             else {
1063 0         0 confess "did not get a new Crypt::PK::ECC object";
1064             }
1065             }
1066              
1067             ##
1068             ## _load_dsa_key($key_text)
1069             ##
1070             ## Arguments:
1071             ## $key_text: string DSA Private Key as String
1072             ##
1073             ## Returns: nothing
1074             ##
1075             ## Populate:
1076             ## self->{KeyInfo}
1077             ## self->{key_obj}
1078             ## self->{key_type}
1079             ##
1080             sub _load_dsa_key {
1081 0     0   0 my $self = shift;
1082 0         0 my $key_text = shift;
1083              
1084 0         0 eval {
1085 0         0 require Crypt::OpenSSL::DSA;
1086             };
1087              
1088 0 0       0 confess "Crypt::OpenSSL::DSA needs to be installed so that we can handle DSA keys." if $@;
1089              
1090 0         0 my $dsa_key = Crypt::OpenSSL::DSA->read_priv_key_str( $key_text );
1091              
1092 0 0       0 if ( $dsa_key ) {
1093 0         0 $self->{ key_obj } = $dsa_key;
1094 0         0 my $g = encode_base64( $dsa_key->get_g(), '' );
1095 0         0 my $p = encode_base64( $dsa_key->get_p(), '' );
1096 0         0 my $q = encode_base64( $dsa_key->get_q(), '' );
1097 0         0 my $y = encode_base64( $dsa_key->get_pub_key(), '' );
1098              
1099 0         0 $self->{KeyInfo} = "<dsig:KeyInfo>
1100             <dsig:KeyValue>
1101             <dsig:DSAKeyValue>
1102             <dsig:P>$p</dsig:P>
1103             <dsig:Q>$q</dsig:Q>
1104             <dsig:G>$g</dsig:G>
1105             <dsig:Y>$y</dsig:Y>
1106             </dsig:DSAKeyValue>
1107             </dsig:KeyValue>
1108             </dsig:KeyInfo>";
1109 0         0 $self->{key_type} = 'dsa';
1110             }
1111             else {
1112 0         0 confess "did not get a new Crypt::OpenSSL::RSA object";
1113             }
1114             }
1115              
1116             ##
1117             ## _load_rsa_key($key_text)
1118             ##
1119             ## Arguments:
1120             ## $key_text: string RSA Private Key as String
1121             ##
1122             ## Returns: nothing
1123             ##
1124             ## Populate:
1125             ## self->{KeyInfo}
1126             ## self->{key_obj}
1127             ## self->{key_type}
1128             ##
1129             sub _load_rsa_key {
1130 3     3   6 my $self = shift;
1131 3         10 my ($key_text) = @_;
1132              
1133 3         7 eval {
1134 3         25 require Crypt::OpenSSL::RSA;
1135             };
1136 3 50       14 confess "Crypt::OpenSSL::RSA needs to be installed so that we can handle RSA keys." if $@;
1137              
1138 3         211 my $rsaKey = Crypt::OpenSSL::RSA->new_private_key( $key_text );
1139              
1140 3 50       30 if ( $rsaKey ) {
1141 3         19 $rsaKey->use_pkcs1_padding();
1142 3         9 $self->{ key_obj } = $rsaKey;
1143 3         10 $self->{ key_type } = 'rsa';
1144              
1145 3 100       14 if (!$self->{ x509 }) {
1146 1         12 my $bigNum = ( $rsaKey->get_key_parameters() )[1];
1147 1         1155 my $bin = $bigNum->to_bin();
1148 1         8 my $exp = encode_base64( $bin, '' );
1149              
1150 1         31 $bigNum = ( $rsaKey->get_key_parameters() )[0];
1151 1         33 $bin = $bigNum->to_bin();
1152 1         5 my $mod = encode_base64( $bin, '' );
1153 1         9 $self->{KeyInfo} = "<dsig:KeyInfo>
1154             <dsig:KeyValue>
1155             <dsig:RSAKeyValue>
1156             <dsig:Modulus>$mod</dsig:Modulus>
1157             <dsig:Exponent>$exp</dsig:Exponent>
1158             </dsig:RSAKeyValue>
1159             </dsig:KeyValue>
1160             </dsig:KeyInfo>";
1161             }
1162             }
1163             else {
1164 0         0 confess "did not get a new Crypt::OpenSSL::RSA object";
1165             }
1166             }
1167              
1168             ##
1169             ## _load_x509_key($key_text)
1170             ##
1171             ## Arguments:
1172             ## $key_text: string RSA Private Key as String
1173             ##
1174             ## Returns: nothing
1175             ##
1176             ## Populate:
1177             ## self->{key_obj}
1178             ## self->{key_type}
1179             ##
1180             sub _load_x509_key {
1181 0     0   0 my $self = shift;
1182 0         0 my $key_text = shift;
1183              
1184 0         0 eval {
1185 0         0 require Crypt::OpenSSL::X509;
1186             };
1187 0 0       0 confess "Crypt::OpenSSL::X509 needs to be installed so that we
1188             can handle X509 Certificates." if $@;
1189              
1190 0         0 my $x509Key = Crypt::OpenSSL::X509->new_private_key( $key_text );
1191              
1192 0 0       0 if ( $x509Key ) {
1193 0         0 $x509Key->use_pkcs1_padding();
1194 0         0 $self->{ key_obj } = $x509Key;
1195 0         0 $self->{key_type} = 'x509';
1196             }
1197             else {
1198 0         0 confess "did not get a new Crypt::OpenSSL::X509 object";
1199             }
1200             }
1201              
1202             ##
1203             ## _load_cert_file()
1204             ##
1205             ## Arguments: none
1206             ##
1207             ## Returns: nothing
1208             ##
1209             ## Read the file name from $self->{ cert } and
1210             ## Populate:
1211             ## self->{key_obj}
1212             ## $self->{KeyInfo}
1213             ##
1214             sub _load_cert_file {
1215 3     3   6 my $self = shift;
1216              
1217 3         6 eval {
1218 3         19 require Crypt::OpenSSL::X509;
1219             };
1220              
1221 3 50       10 confess "Crypt::OpenSSL::X509 needs to be installed so that we can handle X509 certs." if $@;
1222              
1223 3         10 my $file = $self->{ cert };
1224 3 50       128 if ( open my $CERT, '<', $file ) {
1225 3         12 my $text = '';
1226 3         16 local $/ = undef;
1227 3         103 $text = <$CERT>;
1228 3         38 close $CERT;
1229              
1230 3         304 my $cert = Crypt::OpenSSL::X509->new_from_string($text);
1231 3 50       36 if ( $cert ) {
1232 3         12 $self->{ cert_obj } = $cert;
1233 3         112 my $cert_text = $cert->as_string;
1234 3         42 $cert_text =~ s/-----[^-]*-----//gm;
1235 3         19 $self->{KeyInfo} = "<dsig:KeyInfo><dsig:X509Data><dsig:X509Certificate>\n"._trim($cert_text)."\n</dsig:X509Certificate></dsig:X509Data></dsig:KeyInfo>";
1236             }
1237             else {
1238 0         0 confess "Could not load certificate from $file";
1239             }
1240             }
1241             else {
1242 0         0 confess "Could not find certificate file $file";
1243             }
1244              
1245 3         17 return;
1246             }
1247              
1248             ##
1249             ## _load_cert_text()
1250             ##
1251             ## Arguments: none
1252             ##
1253             ## Returns: nothing
1254             ##
1255             ## Read the certificate from $self->{ cert_text } and
1256             ## Populate:
1257             ## self->{key_obj}
1258             ## $self->{KeyInfo}
1259             ##
1260             sub _load_cert_text {
1261 1     1   2 my $self = shift;
1262              
1263 1         2 eval {
1264 1         10 require Crypt::OpenSSL::X509;
1265             };
1266              
1267 1 50       4 confess "Crypt::OpenSSL::X509 needs to be installed so that we can handle X509 certs." if $@;
1268              
1269 1         4 my $text = $self->{ cert_text };
1270 1         99 my $cert = Crypt::OpenSSL::X509->new_from_string($text);
1271 1 50       7 if ( $cert ) {
1272 1         3 $self->{ cert_obj } = $cert;
1273 1         23 my $cert_text = $cert->as_string;
1274 1         13 $cert_text =~ s/-----[^-]*-----//gm;
1275 1         6 $self->{KeyInfo} = "<dsig:KeyInfo><dsig:X509Data><dsig:X509Certificate>\n"._trim($cert_text)."\n</dsig:X509Certificate></dsig:X509Data></dsig:KeyInfo>";
1276             }
1277             else {
1278 0         0 confess "Could not load certificate from given text.";
1279             }
1280              
1281 1         3 return;
1282             }
1283              
1284             ##
1285             ## _load_key($file)
1286             ##
1287             ## Arguments: $self->{ key }
1288             ##
1289             ## Returns: nothing
1290             ##
1291             ## Load the key and process it acording to its headers
1292             ##
1293             sub _load_key {
1294 3     3   9 my $self = shift;
1295 3         8 my $file = $self->{ key };
1296              
1297 3 50       206 if ( open my $KEY, '<', $file ) {
1298 3         12 my $text = '';
1299 3         17 local $/ = undef;
1300 3         101 $text = <$KEY>;
1301 3         45 close $KEY;
1302              
1303 3 50       32 if ( $text =~ m/BEGIN ([DR]SA) PRIVATE KEY/ ) {
    0          
    0          
    0          
1304 3         12 my $key_used = $1;
1305              
1306 3 50       16 if ( $key_used eq 'RSA' ) {
1307 3         19 $self->_load_rsa_key( $text );
1308             }
1309             else {
1310 0         0 $self->_load_dsa_key( $text );
1311             }
1312              
1313 3         23 return 1;
1314             } elsif ( $text =~ m/BEGIN EC PRIVATE KEY/ ) {
1315 0         0 $self->_load_ecdsa_key( $text );
1316             } elsif ( $text =~ m/BEGIN PRIVATE KEY/ ) {
1317 0         0 $self->_load_rsa_key( $text );
1318             } elsif ($text =~ m/BEGIN CERTIFICATE/) {
1319 0         0 $self->_load_x509_key( $text );
1320             }
1321             else {
1322 0         0 confess "Could not detect type of key $file.";
1323             }
1324             }
1325             else {
1326 0         0 confess "Could not load key $file: $!";
1327             }
1328              
1329 0         0 return;
1330             }
1331              
1332             ##
1333             ## _signature_xml($signed_info,$signature_value)
1334             ##
1335             ## Arguments:
1336             ## $signed_info: string XML String Fragment
1337             ## $signature_value String Base64 Signature Value
1338             ##
1339             ## Returns: string XML fragment
1340             ##
1341             ## Create a XML string of the Signature
1342             ##
1343             sub _signature_xml {
1344 4     4   10 my $self = shift;
1345 4         11 my ($signed_info,$signature_value) = @_;
1346 4         100 return qq{<dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
1347             $signed_info
1348             <dsig:SignatureValue>$signature_value</dsig:SignatureValue>
1349             $self->{KeyInfo}
1350             </dsig:Signature>};
1351             }
1352              
1353             ##
1354             ## _signedinfo_xml($digest_xml)
1355             ##
1356             ## Arguments:
1357             ## $digest_xml string XML String Fragment
1358             ##
1359             ## Returns: string XML fragment
1360             ##
1361             ## Create a XML string of the SignedInfo
1362             ##
1363             sub _signedinfo_xml {
1364 4     4   8 my $self = shift;
1365 4         11 my ($digest_xml) = @_;
1366              
1367 4         6 my $algorithm;
1368 4 50 33     27 if ( $self->{ sig_hash } eq 'sha1' && $self->{key_type} ne 'ecdsa' ) {
    0 0        
    0          
1369 4         15 $algorithm = "http://www.w3.org/2000/09/xmldsig#$self->{key_type}-$self->{ sig_hash }";
1370             }
1371             elsif ( $self->{key_type} eq 'ecdsa' ) {
1372 0         0 $algorithm = "http://www.w3.org/2001/04/xmldsig-more#$self->{key_type}-$self->{ sig_hash }";
1373             }
1374             elsif ( $self->{ key_type } eq 'dsa' && $self->{ sig_hash } eq 'sha256') {
1375 0         0 $algorithm = "http://www.w3.org/2009/xmldsig11#$self->{key_type}-$self->{ sig_hash }";
1376             }
1377             else {
1378 0         0 $algorithm = "http://www.w3.org/2001/04/xmldsig-more#$self->{key_type}-$self->{ sig_hash }";
1379             }
1380              
1381             #return qq{<dsig:SignedInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
1382 4         50 return qq{<dsig:SignedInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
1383             <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
1384             <dsig:SignatureMethod Algorithm="$algorithm" />
1385             $digest_xml
1386             </dsig:SignedInfo>};
1387             }
1388              
1389             ##
1390             ## _reference_xml($id)
1391             ##
1392             ## Arguments:
1393             ## $id string XML ID related to the URI
1394             ## $digest string Base64 encoded digest
1395             ##
1396             ## Returns: string XML fragment
1397             ##
1398             ## Create a XML string of the Reference
1399             ##
1400             sub _reference_xml {
1401 4     4   11 my $self = shift;
1402 4         8 my $id = shift;
1403 4         9 my ($digest) = @_;
1404              
1405 4         9 my $algorithm;
1406 4 50 0     16 if ( $self->{ digest_hash } eq 'sha1') {
    0          
1407 4         14 $algorithm = "http://www.w3.org/2000/09/xmldsig#$self->{ digest_hash }";
1408             }
1409             elsif (($self->{ digest_hash } eq 'sha224') || ($self->{ digest_hash } eq 'sha384')) {
1410 0         0 $algorithm = "http://www.w3.org/2001/04/xmldsig-more#$self->{ digest_hash }";
1411             }
1412             else {
1413 0         0 $algorithm = "http://www.w3.org/2001/04/xmlenc#$self->{ digest_hash }";
1414             }
1415              
1416 4         42 return qq{<dsig:Reference URI="#$id">
1417             <dsig:Transforms>
1418             <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
1419             <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
1420             </dsig:Transforms>
1421             <dsig:DigestMethod Algorithm="$algorithm" />
1422             <dsig:DigestValue>$digest</dsig:DigestValue>
1423             </dsig:Reference>};
1424             }
1425              
1426              
1427             ##
1428             ## _canonicalize_xml($xml, $context)
1429             ##
1430             ## Arguments:
1431             ## $xml: string XML NodeSet
1432             ## $context: string XML Context
1433             ##
1434             ## Returns: string Canonical XML
1435             ##
1436             ## Canonicalizes xml based on the CanonicalizationMethod
1437             ## from the SignedInfo.
1438             ##
1439             sub _canonicalize_xml {
1440 10     10   22 my $self = shift;
1441 10         33 my ($xml, $context) = @_;
1442              
1443 10 50       34 print ("_canonicalize_xml:\n") if $DEBUG;
1444             my $canon_method = $self->{ parser }->findnodes(
1445 10         38 'dsig:SignedInfo/dsig:CanonicalizationMethod', $context
1446             );
1447              
1448 10         466 foreach my $node ($canon_method->get_nodelist) {
1449 10         78 my $alg = $node->getAttribute('Algorithm');
1450              
1451 10 50       149 print (" Canon Method: $alg\n") if $DEBUG;
1452 10 50       74 if ($alg eq TRANSFORM_C14N) {
    100          
    50          
    50          
    50          
    0          
1453 0 0       0 print (" toStringC14N\n") if $DEBUG;
1454 0         0 $xml = $xml->toStringC14N();
1455             }
1456             elsif ($alg eq TRANSFORM_C14N_COMMENTS) {
1457 9 50       23 print (" toStringC14N_Comments\n") if $DEBUG;
1458 9         39 $xml = $xml->toStringC14N(1);
1459             }
1460             elsif ($alg eq TRANSFORM_C14N_V1_1) {
1461 0 0       0 print (" toStringC14N_v1_1\n") if $DEBUG;
1462 0         0 $xml = $xml->toStringC14N_v1_1();
1463             }
1464             elsif ($alg eq TRANSFORM_C14N_V1_1_COMMENTS) {
1465 0 0       0 print (" toStringC14N_v1_1_Comments\n") if $DEBUG;
1466 0         0 $xml = $xml->toStringC14N_v1_1(1);
1467             }
1468             elsif ($alg eq TRANSFORM_EXC_C14N) {
1469 1 50       4 print (" toStringEC14N\n") if $DEBUG;
1470 1         11 $xml = $xml->toStringEC14N();
1471             }
1472             elsif ($alg eq TRANSFORM_EXC_C14N_COMMENTS) {
1473 0 0       0 print (" toStringEC14N_Comments\n") if $DEBUG;
1474 0         0 $xml = $xml->toStringEC14N(1);
1475             }
1476             else {
1477 0         0 die "Unsupported transform: $alg";
1478             }
1479             }
1480 10         1928 return $xml;
1481             }
1482             1;
1483              
1484             =pod
1485              
1486             =encoding UTF-8
1487              
1488             =head1 NAME
1489              
1490             Net::SAML2::XML::Sig - Net::SAML2::XML::Sig - A toolkit to help sign and verify XML Digital Signatures
1491              
1492             =head1 VERSION
1493              
1494             version 0.41
1495              
1496             =head1 SYNOPSIS
1497              
1498             my $xml = '<foo ID="abc">123</foo>';
1499             my $signer = Net::SAML2::XML::Sig->new({
1500             key => 'path/to/private.key',
1501             });
1502              
1503             # create a signature
1504             my $signed = $signer->sign($xml);
1505             print "Signed XML: $signed\n";
1506              
1507             # verify a signature
1508             $signer->verify($signed)
1509             or die "Signature Invalid.";
1510             print "Signature valid.\n";
1511              
1512             =head1 DESCRIPTION
1513              
1514             This perl module provides two primary capabilities: given an XML string, create
1515             and insert digital signatures, or if one is already present in the string verify
1516             it -- all in accordance with the W3C standard governing XML signatures.
1517              
1518             =head1 NAME
1519              
1520             Net::SAML2::XML::Sig - A toolkit to help sign and verify XML Digital Signatures.
1521              
1522             =head1 PREREQUISITES
1523              
1524             =over
1525              
1526             =item * L<Digest::SHA>
1527              
1528             =item * L<XML::LibXML>
1529              
1530             =item * L<MIME::Base64>
1531              
1532             =item * L<Crypt::OpenSSL::X509>
1533              
1534             =item * L<Crypt::OpenSSL::Bignum>
1535              
1536             =item * L<Crypt::OpenSSL::RSA>
1537              
1538             =item * L<Crypt::OpenSSL::DSA>
1539              
1540             =item * L<Crypt::PK::ECC>
1541              
1542             =back
1543              
1544             =head1 USAGE
1545              
1546             =head2 SUPPORTED ALGORITHMS & TRANSFORMS
1547              
1548             This module supports the following signature methods:
1549              
1550             =over
1551              
1552             =item * DSA
1553              
1554             =item * RSA
1555              
1556             =item * RSA encoded as x509
1557              
1558             =item * ECDSA
1559              
1560             =item * ECDSA encoded as x509
1561              
1562             =back
1563              
1564             This module supports the following canonicalization methods and transforms:
1565              
1566             =over
1567              
1568             =item * Enveloped Signature
1569              
1570             =item * REC-xml-c14n-20010315#
1571              
1572             =item * REC-xml-c14n-20010315#WithComments
1573              
1574             =item * REC-xml-c14n11-20080502
1575              
1576             =item * REC-xml-c14n11-20080502#WithComments
1577              
1578             =item * xml-exc-c14n#
1579              
1580             =item * xml-exc-c14n#WithComments
1581              
1582             =back
1583              
1584             =head2 OPTIONS
1585              
1586             Each of the following options are also accessors on the main
1587             Net::SAML2::XML::Sig object. TODO Not strictly correct rewrite
1588              
1589             =over
1590              
1591             =item B<key>
1592              
1593             The path to a file containing the contents of a private key. This option
1594             is used only when generating signatures.
1595              
1596             =item B<cert>
1597              
1598             The path to a file containing a PEM-formatted X509 certificate. This
1599             option is used only when generating signatures with the "x509"
1600             option. This certificate will be embedded in the signed document, and
1601             should match the private key used for the signature.
1602              
1603             =item B<cert_text>
1604              
1605             A string containing a PEM-formatted X509 certificate. This
1606             option is used only when generating signatures with the "x509"
1607             option. This certificate will be embedded in the signed document, and
1608             should match the private key used for the signature.
1609              
1610             =item B<x509>
1611              
1612             Takes a true (1) or false (0) value and indicates how you want the
1613             signature to be encoded. When true, the X509 certificate supplied will
1614             be encoded in the signature. Otherwise the native encoding format for
1615             RSA, DSA and ECDSA will be used.
1616              
1617             =item B<sig_hash>
1618              
1619             Passing sig_hash to new allows you to specify the SignatureMethod
1620             hashing algorithm used when signing the SignedInfo. RSA and ECDSA
1621             supports the hashes specified sha1, sha224, sha256, sha384 and sha512
1622              
1623             DSA supports only sha1 and sha256 (but you really should not sign
1624             anything with DSA anyway).
1625              
1626             =item B<digest_hash>
1627              
1628             Passing digest_hash to new allows you to specify the DigestMethod
1629             hashing algorithm used when calculating the hash of the XML being
1630             signed. Supported hashes can be specified sha1, sha224, sha256,
1631             sha384, and sha512
1632              
1633             =item B<no_xml_declaration>
1634              
1635             Some applications such as Net::SAML2 expect to sign a fragment of the
1636             full XML document so is this is true (1) it will not include the
1637             XML Declaration at the beginning of the signed XML. False (0) or
1638             undefined returns an XML document starting with the XML Declaration.
1639              
1640             =back
1641              
1642             =head2 METHODS
1643              
1644             =head3 B<new(...)>
1645              
1646             Constructor; see OPTIONS above.
1647              
1648             =head3 B<sign($xml)>
1649              
1650             When given a string of XML, it will return the same string with a signature
1651             generated from the key provided when the Net::SAML2::XML::Sig object was initialized.
1652              
1653             This method will sign all elements in your XML with an ID (case sensitive)
1654             attribute. Each element with an ID attribute will be the basis for a seperate
1655             signature. It will correspond to the URI attribute in the Reference element
1656             that will be contained by the signature. If no ID attribute can be found on
1657             an element, the signature will not be created.
1658              
1659             The elements are signed in reverse order currently assuming (possibly
1660             incorrectly) that the lower element in the tree may need to be signed
1661             inclusive of its Signature because it is a child of the higher element.
1662              
1663             Arguments:
1664             $xml: string XML string
1665              
1666             Returns: string Signed XML
1667              
1668             =head3 B<verify($xml)>
1669              
1670             Returns true or false based upon whether the signature is valid or not.
1671              
1672             When using Net::SAML2::XML::Sig exclusively to verify a signature, no key needs to be
1673             specified during initialization given that the public key should be
1674             transmitted with the signature.
1675              
1676             Net::SAML2::XML::Sig checks all signature in the provided xml and will fail should any
1677             signature pointing to an existing ID in the XML fail to verify.
1678              
1679             Should there be a Signature included that does not point to an existing node
1680             in the XML it is ignored and other Signaures are checked. If there are no
1681             other Signatures it will return false.
1682              
1683             Arguments:
1684             $xml: string XML string
1685              
1686             Returns: string Signed XML
1687              
1688             =head3 B<signer_cert()>
1689              
1690             Following a successful verify with an X509 certificate, returns the
1691             signer's certificate as embedded in the XML document for verification
1692             against a CA certificate. The certificate is returned as a
1693             Crypt::OpenSSL::X509 object.
1694              
1695             Arguments: none
1696              
1697             Returns: Crypt::OpenSSL::X509: Certificate used to sign the XML
1698              
1699             =head1 ABOUT DIGITAL SIGNATURES
1700              
1701             Just as one might want to send an email message that is cryptographically signed
1702             in order to give the recipient the means to independently verify who sent the email,
1703             one might also want to sign an XML document. This is especially true in the
1704             scenario where an XML document is received in an otherwise unauthenticated
1705             context, e.g. SAML.
1706              
1707             However XML provides a challenge that email does not. In XML, two documents can be
1708             byte-wise inequivalent, and semanticaly equivalent at the same time. For example:
1709              
1710             <?xml version="1.0"?>
1711             <foo>
1712             <bar />
1713             </foo>
1714              
1715             And:
1716              
1717             <?xml version="1.0"?>
1718             <foo>
1719             <bar></bar>
1720             </foo>
1721              
1722             Each of these document express the same thing, or in other words they "mean"
1723             the same thing. However if you were to strictly sign the raw text of these
1724             documents, they would each produce different signatures.
1725              
1726             XML Signatures on the other hand will produce the same signature for each of
1727             the documents above. Therefore an XML document can be written and rewritten by
1728             different parties and still be able to have someone at the end of the line
1729             verify a signature the document may contain.
1730              
1731             There is a specially subscribed methodology for how this process should be
1732             executed and involves transforming the XML into its canonical form so a
1733             signature can be reliably inserted or extracted for verification. This
1734             module implements that process.
1735              
1736             =head2 EXAMPLE SIGNATURE
1737              
1738             Below is a sample XML signature to give you some sense of what they look like.
1739             First let's look at the original XML document, prior to being signed:
1740              
1741             <?xml version="1.0"?>
1742             <foo ID="abc">
1743             <bar>123</bar>
1744             </foo>
1745              
1746             Now, let's insert a signature:
1747              
1748             <?xml version="1.0"?>
1749             <foo ID="abc">
1750             <bar>123</bar>
1751             <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
1752             <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
1753             <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" />
1754             <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
1755             <Reference URI="#abc">
1756             <Transforms>
1757             <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
1758             </Transforms>
1759             <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
1760             <DigestValue>9kpmrvv3peVJpNSTRycrV+jeHVY=</DigestValue>
1761             </Reference>
1762             </SignedInfo>
1763             <SignatureValue>
1764             HXUBnMgPJf//j4ihaWnaylNwAR5AzDFY83HljFIlLmTqX1w1C72ZTuRObvYve8TNEbVsQlTQkj4R
1765             hiY0pgIMQUb75GLYFtc+f0YmBZf5rCWY3NWzo432D3ogAvpEzYXEQPmicWe2QozQhybaz9/wrYki
1766             XiXY+57fqCkf7aT8Bb6G+fn7Aj8gnZFLkmKxwCdyGsIZOIZdQ8MWpeQrifxBR0d8W1Zm6ix21WNv
1767             ONt575h7VxLKw8BDhNPS0p8CS3hOnSk29stpiDMCHFPxAwrbKVL1kGDLaLZn1q8nNRmH8oFxG15l
1768             UmS3JXDZAss8gZhU7g9T4XllCqjrAvzPLOFdeQ==
1769             </SignatureValue>
1770             <KeyInfo>
1771             <KeyValue>
1772             <RSAKeyValue>
1773             <Modulus>
1774             1b+m37u3Xyawh2ArV8txLei251p03CXbkVuWaJu9C8eHy1pu87bcthi+T5WdlCPKD7KGtkKn9vq
1775             i4BJBZcG/Y10e8KWVlXDLg9gibN5hb0Agae3i1cCJTqqnQ0Ka8w1XABtbxTimS1B0aO1zYW6d+U
1776             Yl0xIeAOPsGMfWeu1NgLChZQton1/NrJsKwzMaQy1VI8m4gUleit9Z8mbz9bNMshdgYEZ9oC4bH
1777             n/SnA4FvQl1fjWyTpzL/aWF/bEzS6Qd8IBk7yhcWRJAGdXTWtwiX4mXb4h/2sdrSNvyOsd/shCf
1778             OSMsf0TX+OdlbH079AsxOwoUjlzjuKdCiFPdU6yAJw==
1779             </Modulus>
1780             <Exponent>Iw==</Exponent>
1781             </RSAKeyValue>
1782             </KeyValue>
1783             </KeyInfo>
1784             </Signature>
1785             </foo>
1786              
1787             =head1 SEE ALSO
1788              
1789             L<http://www.w3.org/TR/xmldsig-core/>
1790              
1791             =head1 VERSION CONTROL
1792              
1793             L<https://github.com/perl-net-saml2/perl-XML-Sig>
1794              
1795             =head1 AUTHORS and CREDITS
1796              
1797             Author: Byrne Reese <byrne@majordojo.com>
1798              
1799             Thanks to Manni Heumann who wrote Google::SAML::Response from
1800             which this module borrows heavily in order to create digital
1801             signatures.
1802              
1803             Net::SAML2 embedded version amended by Chris Andrews <chris@nodnol.org>.
1804              
1805             Maintainer: Timothy Legge <timlegge@cpan.org>
1806              
1807             =head1 AUTHOR
1808              
1809             Chris Andrews <chrisa@cpan.org>
1810              
1811             =head1 COPYRIGHT AND LICENSE
1812              
1813             This software is copyright (c) 2021 by Chris Andrews and Others, see the git log.
1814              
1815             This is free software; you can redistribute it and/or modify it under
1816             the same terms as the Perl 5 programming language system itself.
1817              
1818             =cut
1819              
1820             __END__
1821              
1822             }
1823