File Coverage

blib/lib/XML/Sig.pm
Criterion Covered Total %
statement 606 675 89.7
branch 181 310 58.3
condition 35 51 68.6
subroutine 64 68 94.1
pod 5 5 100.0
total 891 1109 80.3


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