File Coverage

blib/lib/XML/Sig.pm
Criterion Covered Total %
statement 415 468 88.6
branch 100 182 54.9
condition 1 6 16.6
subroutine 43 47 91.4
pod 4 4 100.0
total 563 707 79.6


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