File Coverage

blib/lib/XML/Sig/OO.pm
Criterion Covered Total %
statement 451 461 97.8
branch 105 170 61.7
condition 6 12 50.0
subroutine 68 69 98.5
pod 42 43 97.6
total 672 755 89.0


line stmt bran cond sub pod time code
1             package XML::Sig::OO;
2              
3             our $VERSION="0.006";
4              
5 1     1   1274 use Modern::Perl;
  1         3  
  1         10  
6 1     1   858 use Moo;
  1         8138  
  1         7  
7 1     1   2904 use MooX::Types::MooseLike::Base qw(:all);
  1         10771  
  1         404  
8 1     1   13 use MIME::Base64;
  1         2  
  1         76  
9 1     1   998 use XML::LibXML;
  1         45082  
  1         8  
10 1     1   176 use XML::LibXML::XPathContext;
  1         3  
  1         24  
11 1     1   1144 use Crypt::OpenSSL::X509;
  1         17013  
  1         77  
12 1     1   596 use Crypt::OpenSSL::RSA;
  1         8659  
  1         54  
13 1     1   9 use Crypt::OpenSSL::Bignum;
  1         2  
  1         27  
14 1     1   499 use Crypt::OpenSSL::DSA;
  1         1672  
  1         35  
15 1     1   514 use Crypt::OpenSSL::VerifyX509;
  1         984  
  1         38  
16 1     1   1050 use Digest::SHA qw(sha1);
  1         3458  
  1         78  
17 1     1   529 use Ref::Util qw( is_plain_hashref);
  1         1760  
  1         64  
18 1     1   479 use Data::Result;
  1         51550  
  1         35  
19 1     1   7 use Carp qw(croak);
  1         17  
  1         48  
20 1     1   6 use Scalar::Util qw(looks_like_number);
  1         20  
  1         48  
21 1     1   6 use namespace::clean;
  1         1  
  1         6  
22 1     1   1222 use constant TRANSFORM_EXC_C14N => 'http://www.w3.org/2001/10/xml-exc-c14n#';
  1         3  
  1         66  
23 1     1   7 use constant TRANSFORM_EXC_C14N_COMMENTS => 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
  1         1  
  1         9300  
24              
25             =head1 NAME
26              
27             XML::Sig::OO - Modern XML Signatured validation
28              
29             =head1 SYNOPSIS
30              
31             use XML::Sig::OO;
32              
33             # Sign our xml
34             my $s=new XML::Sig::OO(
35             xml=>'<?xml version="1.0" standalone="yes"?><data><test ID="A" /><test ID="B" /></data>',
36             key_file=>'rsa_key.pem'
37             cert_file=>'cert.pem',
38             );
39             my $result=$s->sign;
40             die "Failed to sign the xml, error was: $result" unless $result;
41              
42             my $xml=$result->get_data;
43             # Example checking a signature
44             my $v=new XML::Sig::OO(xml=>$xml);
45              
46             # validate our xml
47             my $result=$v->validate;
48              
49             if($result) {
50             print "everything checks out!\n";
51             } else {
52             foreach my $chunk (@{$result->get_data}) {
53             my ($nth,$signature,$digest)=@{$chunk}{qw(nth signature digest)};
54              
55             print "Results for processing chunk $nth\n";
56             print "Signature State: ".($signature ? "OK\n" : "Failed, error was $signature\n");
57             print "Digest State: ".($digest ? "OK\n" : "Failed, error was $digest\n");
58             }
59             }
60              
61             =head1 DESCRIPTION
62              
63             L<XML::Sig::OO> is a project to create a stand alone perl module that does a good job creating and validating xml signatures. At its core This module is written around libxml2 better known as L<XML::LibXML>.
64              
65             =head1 Multiple signatures and keys
66              
67             In the case of signing multiple //@ID elements, it is possible to sign each chunk with a different key, in fact you can even use completly different key types.
68              
69             use Modern::Perl;
70             use XML::Sig::OO;
71             use File::Spec;
72             use FindBin qw($Bin);
73             use Crypt::OpenSSL::DSA;
74             use Crypt::OpenSSL::RSA;
75              
76             # create our signign object
77             my $s=new XML::Sig::OO(
78             xml=>'<?xml version="1.0" standalone="yes"?><data><test ID="A" /><test ID="B" /></data>',
79             );
80              
81             my $x=$s->build_xpath;
82              
83             # sign our first xml chunk with our rsa key!
84             my $rsa_str=join '',IO::File->new(File::Spec->catfile($Bin,'x509_key.pem'))->getlines;
85             my $rsa=Crypt::OpenSSL::RSA->new_private_key($rsa_str);
86             $rsa->use_pkcs1_padding();
87             my $cert_str=join '',IO::File->new(File::Spec->catfile($Bin,'x509_cert.pem'))->getlines;
88             $s->sign_cert($rsa);
89             $s->key_type('rsa');
90             $s->cert_string($cert_str);
91             my $result=$s->sign_chunk($x,1);
92             die $result unless $result;
93              
94             # Sign our 2nd chunk with our dsa key
95             my $dsa = Crypt::OpenSSL::DSA->read_priv_key(File::Spec->catfile($Bin,'dsa_priv.pem'));
96             $s->cert_string(undef);
97             $s->sign_cert($dsa);
98             $s->key_type('dsa');
99             $result=$s->sign_chunk($x,2);
100             die $result unless $result;
101              
102             my ($node)=$x->findnodes($s->xpath_Root);
103             my $xml=$node->toString;
104              
105             print "Our Signed XML IS: \n",$xml,"\n";
106             # Example checking a signature
107             my $v=new XML::Sig::OO(xml=>$xml);
108              
109             $result=$v->validate;
110             die $result unless $result;
111              
112             print "Our signed and xml passes validation\n";
113              
114             =head2 Working with Net::SAML2
115              
116             L<Net::SAML2> has many problems when it comes to signature validation of xml strings. This section documents how to use this module in place of the Net::SAML2 built ins.
117              
118             use Net::SAML2::Protocol::Assertion;
119             use XML::Sig::OO;
120             use MIME::Base64;
121              
122             # Lets assume we have a post binding response
123             my $saml_response=.....
124              
125             my $xml=decode_base64($saml_response);
126              
127             my $v=XML::Sig::OO->new(xml=>$xml,cacert=>'idp_cert.pem');
128             my $result=$v->validate;
129             die $result unless $result;
130              
131             # we can now use the asertion knowing it was from our idp
132             my $assertion=Net::SAML2::Protocol::Assertion->new_from_xml(xml=>$xml)
133              
134             =head2 Encrypted keys
135              
136             Although this package does not directly support encrypted keys, it is possible to use encrypted keys by loading and exporting them with the L<Crypt::PK::RSA> and L<Crypt::PK::DSA> packages.
137              
138             =head1 Constructor options
139              
140             =cut
141              
142             =over 4
143              
144             =item * xml=>'...'
145              
146             The base xml string to validate or sign. This option is always required.
147              
148             =cut
149              
150             has xml=>(
151             is=>'ro',
152             isa=>Str,
153             required=>1,
154             );
155              
156             =item * cacert=>'/path/to/your/cacert.pem'
157              
158             Optional, used to validate X509 certs.
159              
160             =cut
161              
162             has cacert=>(
163             is=>'ro',
164             isa=>sub { my ($f)=@_; croak "cacert must be a readable file" unless defined($f) && -r $f },
165             required=>0,
166             );
167              
168             =item * build_parser=>sub { return XML::LibXML->new() }
169              
170             Callback that returns a new XML Parser
171              
172             =cut
173              
174             has build_parser=>(
175             is=>'ro',
176             isa=>CodeRef,
177             default=>sub { sub { XML::LibXML->new() } },
178             );
179              
180             =item * namespaces=>{ ds=>'http://www.w3.org/2000/09/xmldsig#', ec=>'http://www.w3.org/2001/10/xml-exc-c14n#'}
181              
182             Contains the list of namespaces to set in our XML::LibXML::XPathContext object.
183              
184             =cut
185              
186             has namespaces=>(
187             is=>'ro',
188             isa=>HashRef,
189             default=>sub {
190             {
191             ds=>'http://www.w3.org/2000/09/xmldsig#',
192             ec=>'http://www.w3.org/2001/10/xml-exc-c14n#',
193             samlp=>"urn:oasis:names:tc:SAML:2.0:protocol",
194             }
195             },
196             );
197              
198             =item * digest_cbs=>{ ... }
199              
200             Contains the digest callbacks. The default handlers can be found in %XML::SIG::OO::DIGEST.
201              
202             =cut
203              
204             our %DIGEST=(
205             'http://www.w3.org/2000/09/xmldsig#sha1' => sub { my ($self,$content)=@_; $self->_get_digest(sha1 => $content) },
206             'http://www.w3.org/2001/04/xmlenc#sha256' => sub { my ($self,$content)=@_; $self->_get_digest(sha256 => $content) },
207             'http://www.w3.org/2001/04/xmlenc#sha512' => sub { my ($self,$content)=@_; $self->_get_digest(sha512 => $content) },
208             'http://www.w3.org/2001/04/xmldsig-more#sha224' => sub { my ($self,$content)=@_; $self->_get_digest(sha224 => $content) },
209             'http://www.w3.org/2001/04/xmldsig-more#sha384' => sub { my ($self,$content)=@_; $self->_get_digest(sha384 => $content) },
210             'http://www.w3.org/2001/04/xmldsig-more#sha512' => sub { my ($self,$content)=@_; $self->_get_digest(sha512 => $content) },
211             'http://www.w3.org/2001/04/xmldsig-more#sha1024' => sub { my ($self,$content)=@_; $self->_get_digest(sha1024 => $content) },
212             'http://www.w3.org/2001/04/xmldsig-more#sha2048' => sub { my ($self,$content)=@_; $self->_get_digest(sha2048=> $content) },
213             'http://www.w3.org/2001/04/xmldsig-more#sha3072' => sub { my ($self,$content)=@_; $self->_get_digest(sha3072=> $content) },
214             'http://www.w3.org/2001/04/xmldsig-more#sha4096' => sub { my ($self,$content)=@_; $self->_get_digest(sha4096=> $content) },
215             );
216              
217             =item * digest_method=>'http://www.w3.org/2000/09/xmldsig#sha1'
218              
219             Sets the digest method to be used when signing xml
220              
221             =cut
222              
223             has digest_method=>(
224             isa=>sub { exists $DIGEST{$_[0]} or croak "$_[0] is not a supported digest" },
225             is=>'ro',
226             default=>'http://www.w3.org/2000/09/xmldsig#sha1',
227             );
228              
229             =item * key_type=>'rsa'
230              
231             The signature method we will use
232              
233             =cut
234              
235             has key_type=>(
236             isa=>sub { croak "unsuported key type: $_[0]" unless $_[0]=~ /^(?:dsa|rsa|x509)$/s },
237             is=>'rw',
238             required=>0,
239             lazy=>1,
240             default=>'x509',
241             );
242              
243             has digest_cbs=>(
244             isa=>HashRef,
245             is=>'ro',
246             default=>sub { return { %DIGEST} },
247             );
248              
249             sub _get_digest {
250 28     28   66 my ($self,$algo, $content) = @_;
251 28         725 my $digest = Digest::SHA->can("${algo}_base64")->($content);
252 28         98 while (length($digest) % 4) { $digest .= '=' }
  28         79  
253 28         70 return $digest;
254             }
255              
256             our %TUNE_CERT=(
257             'http://www.w3.org/2000/09/xmldsig#dsa-sha1' => sub { _tune_cert(@_,'sha1') },
258             'http://www.w3.org/2000/09/xmldsig#rsa-sha1' => sub { _tune_cert(@_,'sha1') },
259             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224' => sub { _tune_cert(@_,'sha224') },
260             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' => sub { _tune_cert(@_,'sha256') },
261             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384' => sub { _tune_cert(@_,'sha384') },
262             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512' => sub { _tune_cert(@_,'sha512') },
263             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha1024' => sub { _tune_cert(@_,'sha1024') },
264             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha2048' => sub { _tune_cert(@_,'sha2048') },
265             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha3072' => sub { _tune_cert(@_,'sha3072') },
266             'http://www.w3.org/2001/04/xmldsig-more#rsa-sha4096' => sub { _tune_cert(@_,'sha4096') },
267             );
268              
269             =item * signature_method=>'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
270              
271             Sets the signature method.
272              
273             =cut
274              
275             has signature_method=>(
276             isa=>Str,
277             is=>'ro',
278             default=>'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
279             );
280              
281             sub _tune_cert {
282 27     27   47 my ($self,$cert,$alg)=@_;
283              
284 27         64 my $method="use_${alg}_hash";
285              
286 27 100       145 if($cert->can($method)) {
287 26         97 $cert->$method();
288             }
289             }
290              
291             =item * tune_cert_cbs=>{ ...}
292              
293             A collection of callbacks to tune a certificate object for signing
294              
295             =cut
296              
297             has tune_cert_cbs=>(
298             isa=>HashRef,
299             is=>'ro',
300             default=>sub {
301             return {%TUNE_CERT}
302             }
303             );
304              
305             =item * mutate_cbs=>{....}
306              
307             Transform and Canonization callbacks. The default callbacks are defined in %XML::Sig::OO::MUTATE.
308              
309             Callbacks are usied in the following context
310              
311             $cb->($self,$xpath_element);
312              
313             =cut
314              
315             sub _build_canon_coderef {
316 6     6   11 my ($method,$comment)=@_;
317             return sub {
318 57     57   112 my ($self,$x,$node,$nth,$ec14n_inclusive_prefixes)=@_;
319              
320 57 100       136 if ($method eq "toStringEC14N")
321             {
322 32         112 return $node->$method($comment, undef, $ec14n_inclusive_prefixes);
323             }
324             else
325             {
326 25         95 return $node->$method($comment);
327             }
328 6         25 };
329             }
330              
331             sub _envelope_transform {
332 28     28   53 my ($self,$x,$node,$nth)=@_;
333              
334 28         71 my $xpath=$self->context($self->xpath_Signature,$nth);
335 28         83 my ($target)=$x->findnodes($xpath,$node);
336 28 100       1429 $node->removeChild($target) if defined($target);
337 28         221 return $node->toString;
338             }
339              
340             our %MUTATE=(
341             'http://www.w3.org/2000/09/xmldsig#enveloped-signature'=>\&_envelope_transform,
342             'http://www.w3.org/TR/2001/REC-xml-c14n-20010315' => _build_canon_coderef('toStringC14N',0),
343             'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments' => _build_canon_coderef('toStringC14N',1),
344             'http://www.w3.org/2006/12/xml-c14n11' => _build_canon_coderef('toStringC14N_v1_1',0),
345             'http://www.w3.org/2006/12/xml-c14n11#WithComments' => _build_canon_coderef('toStringC14N_v1_1',1),
346             'http://www.w3.org/2001/10/xml-exc-c14n#' => _build_canon_coderef('toStringEC14N',0),
347             'http://www.w3.org/2001/10/xml-exc-c14n#WithComments' => _build_canon_coderef('toStringEC14N',1),
348             );
349              
350             has mutate_cbs=>(
351             isa=>HashRef,
352             is=>'ro',
353             default=>sub { return {%MUTATE} },
354             );
355              
356             =back
357              
358             =head2 Xpaths
359              
360             The xpaths in this package are not hard coded, each xpath can be defined as an argument to the constructor. Since xml can contain multiple elements with signatures or multiple id elements to sign, most xpaths are prefixed with the $nth signature
361              
362             Some cases the xpaths are used in the following context:
363              
364             (/xpath)[$nth]
365              
366             In special cases like finding a list of transforms or which key, signature, or digest:
367              
368             (//ds::Signature)[$nth]/xpath
369              
370             =over 4
371              
372             =item * xpath_SignatureValue=>//ds:SignatureValue
373              
374             Xpath used to find the signature value.
375              
376             =cut
377              
378             has xpath_SignatureValue=>(
379             isa=>Str,
380             is=>'ro',
381             default=>'//ds:SignatureValue',
382             );
383              
384             =item * xpath_SignatureMethod=>'//ds:SignatureMethod/@Algorithm'
385              
386             Xpath used to find the signature method algorithm.
387              
388             =cut
389              
390             has xpath_SignatureMethod=>(
391             isa=>Str,
392             is=>'ro',
393             default=>'//ds:SignatureMethod/@Algorithm',
394             );
395              
396             =item * xpath_CanonicalizationMethod=>'//ds:CanonicalizationMethod/@Algorithm'
397              
398             Xpath used to find the list of canonicalization method(s).
399              
400             =cut
401              
402             has xpath_CanonicalizationMethod=>(
403             is=>Str,
404             is=>'ro',
405             default=>'//ds:CanonicalizationMethod/@Algorithm',
406             );
407              
408             =item * xpath_SignedInfo=>'//ds:SignedInfo'
409              
410             Xpath used to find the singed info.
411              
412             =cut
413              
414             has xpath_SignedInfo=>(
415             is=>'ro',
416             isa=>Str,
417             default=>'//ds:SignedInfo',
418             );
419              
420             =item * xpath_Signature=>'//ds:Signature'
421              
422             Xpath used to fetch the signature value
423              
424             =cut
425              
426             has xpath_Signature=>(
427             is=>'ro',
428             isa=>Str,
429             default=>'//ds:Signature'
430             );
431              
432             =item * xpath_Transforms=>//ds:Transforms
433              
434             Xpath Transform path
435             =cut
436              
437             has xpath_Transforms=>(
438             isa=>Str,
439             is=>'ro',
440             default=>'//ds:Transforms',
441             );
442              
443             =item * xpath_Transform=>'/ds:Transform'
444              
445             Xpath used to find the transform
446              
447             =cut
448              
449             has xpath_Transform=>(
450             isa=>Str,
451             is=>'ro',
452             default=>'/ds:Transform'
453             );
454              
455             =item * xpath_TransformInclusiveNamespacesPrefixList=>'ec:InclusiveNamespaces/@PrefixList'
456              
457             Xpath used to find the transform Algorithm
458              
459             =cut
460              
461             has xpath_TransformInclusiveNamespacesPrefixList=>(
462             isa=>Str,
463             is=>'ro',
464             default=>'ec:InclusiveNamespaces/@PrefixList'
465             );
466              
467             =item * xpath_TransformAlgorithm=>'@Algorithm'
468              
469             Xpath used to find the transform Algorithm
470              
471             =cut
472              
473             has xpath_TransformAlgorithm=>(
474             isa=>Str,
475             is=>'ro',
476             default=>'@Algorithm'
477             );
478              
479             =item * xpath_DigestValue=>'//ds:DigestValue'
480              
481             Xpath used to fetch the digest value
482              
483             =cut
484              
485             has xpath_DigestValue=>(
486             is=>'ro',
487             isa=>Str,
488             default=>'//ds:DigestValue',
489             );
490              
491             =item * xpath_DigestMethod=>'//ds:DigestMethod/@Algorithm'
492              
493             Xpath used to find the digest method.
494              
495             =cut
496              
497             has xpath_DigestMethod=>(
498             is=>'ro',
499             isa=>Str,
500             default=>'//ds:DigestMethod/@Algorithm',
501             );
502              
503             =item * xpath_DigestId=>'//ds:Reference/@URI'
504              
505             Xpath used to find the id of the node that should contain our digest.
506              
507             =cut
508              
509             has xpath_DigestId=>(
510             is=>'ro',
511             isa=>Str,
512             default=>'//ds:Reference/@URI',
513             );
514              
515             =item * digest_id_convert_cb=>sub { my ($self,$xpath_object,$id)=@_;$id =~ s/^#//;return "//*[\@ID='$id']" }
516              
517             Code ref that converts the xpath_DigestId into the xpath lookup ised to find the digest node
518              
519             =cut
520              
521             has digest_id_convert_cb=>(
522             isa=>CodeRef,
523             default=>sub { \&_default_digest_id_conversion },
524             is=>'ro',
525              
526             );
527              
528             sub _default_digest_id_conversion {
529 23     23   45 my ($self,$xpath_object,$id)=@_;
530 23         132 $id=~ s/^#//s;
531 23         95 return "//*[\@ID='$id']";
532             }
533              
534             =item * xpath_ToSign=>'//[@ID]'
535              
536             Xpath used to find what nodes to sign.
537              
538             =cut
539              
540             has xpath_ToSign=>(
541             isa=>Str,
542             is=>'ro',
543             default=>'//*[@ID]',
544             );
545              
546             =item * xpath_IdValue=>'//@ID'
547              
548             Xpath used to find the value of the current id.
549              
550             =cut
551              
552             has xpath_IdValue=>(
553             isa=>Str,
554             is=>'ro',
555             default=>'//@ID',
556             );
557              
558             =item * xpath_Root=>'/'
559              
560             Root of the document expath
561              
562             =cut
563              
564             has xpath_Root=>(
565             isa=>Str,
566             is=>'ro',
567             default=>'/',
568             );
569              
570             =back
571              
572             =head3 XPaths related to certs
573              
574             This section documents all xpaths/options related to certs.
575              
576             =cut
577              
578             =over 4
579              
580             =item * xpath_x509Data=>'/ds:KeyInfo/ds:X509Data/ds:X509Certificate'
581              
582             Xpath used to find the x509 cert value. In reality the nth signature will be prepended to this xpath.
583              
584             Actual xpath used:
585              
586             (//ds:Signature)[$nth]/ds:KeyInfo/ds:X509Data/ds:X509Certificate
587              
588             =cut
589              
590             has xpath_x509Data=>(
591             is=>'ro',
592             isa=>Str,
593             default=>'/ds:KeyInfo/ds:X509Data/ds:X509Certificate',
594             );
595              
596             =item * xpath_RSAKeyValue=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue'
597              
598             Xpath used to find the RSA value tree.
599              
600             =cut
601              
602             has xpath_RSAKeyValue=>(
603             is=>'ro',
604             isa=>Str,
605             default=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue',
606             );
607              
608             =item * xpath_RSA_Modulus=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Modulus'
609              
610             Xpath used to find the RSA Modulus.
611              
612             =cut
613              
614             has xpath_RSA_Modulus=>(
615             is=>'ro',
616             is=>'rw',
617             default=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Modulus',
618             );
619              
620             =item * xpath_RSA_Exponent=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Exponent'
621              
622             Xpath used to find the RSA Exponent.
623              
624             =cut
625              
626             has xpath_RSA_Exponent=>(
627             is=>'ro',
628             is=>'rw',
629             isa=>Str,
630             default=>'/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Exponent',
631             );
632              
633             =item * xpath_DSAKeyValue=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue'
634              
635             Xpath used for DSA key tree discovery.
636              
637             =cut
638              
639             has xpath_DSAKeyValue=>(
640             is=>'ro',
641             isa=>Str,
642             default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue',
643             );
644              
645             =item * xpath_DSA_P=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:P'
646              
647             Xpath used to find DSA_P.
648              
649             =cut
650              
651             has xpath_DSA_P=>(
652             is=>'ro',
653             isa=>Str,
654             default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:P',
655             );
656              
657             =item * xpath_DSA_Q=>''
658              
659             Xpath used to find DSA_Q.
660              
661             =cut
662              
663             has xpath_DSA_Q=>(
664             is=>'ro',
665             isa=>Str,
666             default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Q',
667             );
668              
669             =item * xpath_DSA_G=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:G'
670              
671             Xpath used to find DSA_G.
672              
673             =cut
674              
675             has xpath_DSA_G=>(
676             is=>'ro',
677             isa=>Str,
678             default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:G',
679             );
680              
681             =item * xpath_DSA_Y=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Y'
682              
683             Xpath used to find DSA_Y
684              
685             =cut
686              
687             has xpath_DSA_Y=>(
688             is=>'ro',
689             isa=>Str,
690             default=>'/ds:KeyInfo/ds:KeyValue/ds:DSAKeyValue/ds:Y',
691             );
692              
693             =back
694              
695             =head3 OO Signing Options
696              
697             The following Signature options can be passed to the constructor object.
698              
699             =over 4
700              
701             =item * key_file=>'path/to/my.key'
702              
703             Key file only used when signing.
704              
705             =cut
706              
707             has key_file=>(
708             isa=>Str,
709             required=>0,
710             is=>'ro',
711             );
712              
713             =item * envelope_method=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature"
714              
715             Sets the envelope method; This value most likely is the only valid value.
716              
717             =cut
718              
719             has envelope_method=>(
720             isa=>Str,
721             is=>'ro',
722             default=>"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
723             );
724              
725             #=item * canon_method=>'http://www.w3.org/2001/10/xml-exc-c14n#'
726             =item * canon_method=>'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
727              
728             Sets the canonization method used when signing the code
729              
730             =cut
731              
732             has canon_method=>(
733             isa=>Str,
734             #default=>"http://www.w3.org/2001/10/xml-exc-c14n#",
735             default=>"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
736             is=>'ro',
737             );
738              
739             =item * tag_namespace=>'ds'
740              
741             Default namespace of the tags being created. This must be defined in $self->namespaces.
742              
743             =cut
744              
745             has tag_namespace=>(
746             isa=>Str,
747             default=>'ds',
748             is=>'ro',
749             );
750              
751             =item * sign_cert=>$cert_object
752              
753             Optional: The Certificate object used to sign xml. If this option is set it is recomended that you set the "key_type" option as well.
754              
755             =cut
756              
757             has sign_cert=>(
758             isa=>Object,
759             is=>'rw',
760             required=>0,
761             lazy=>1,
762             );
763              
764             =item * cert_file=>'/path/to/cert.pem'
765              
766             The path that contains the cert file used for signing.
767              
768             =cut
769              
770             has cert_file=>(
771             isa=>sub {
772             my ($file)=@_;
773             croak "$file must be defined" unless defined($file);
774             croak "$file must be readable" unless -r $file;
775             },
776             is=>'rw',
777             required=>0,
778             lazy=>1,
779             );
780              
781             =item * cert_string=>undef
782              
783             This optional argument lets you define the x509 pem text that will be used to generate the x509 portion of the xml.
784              
785             =cut
786              
787             has cert_string=>(
788             is=>'rw',
789             required=>0,
790             lazy=>1,
791             );
792              
793             =back
794              
795             =cut
796              
797             sub BUILD {
798 25     25 0 17656 my ($self)=@_;
799              
800             # sanity check dsa signature method
801 25 50 33     387 croak 'dsa key types only work with signature_method: http://www.w3.org/2000/09/xmldsig#dsa-sha1'
802             if $self->key_type eq 'dsa' && $self->signature_method ne 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
803              
804              
805 25 50       279 croak "namespaces does not contain: ".$self->tag_namespace unless exists $self->namespaces->{$self->tag_namespace};
806 25 50       76 croak $self->signature_method." is an unsupported signature method" unless exists $self->tune_cert_cbs->{$self->signature_method};
807 25 100 66     310 if(defined($self->key_file) && !defined($self->sign_cert)) {
808 5         57 my $result=$self->load_cert_from_file($self->key_file);
809 5 50       1126 croak $result unless $result;
810 5         280 my ($key_type,$cert)=@{$result->get_data}{qw(type cert)};
  5         14  
811 5         103 $self->sign_cert($cert);
812 5         298 $self->key_type($key_type);
813             }
814             }
815              
816             =head1 OO Methods
817              
818             =head2 my $xpath=$self->build_xpath(undef|$xml,{ns=>'url'}|undef);
819              
820             Creates a new xpath object based on our current object state.
821              
822             =cut
823              
824             sub build_xpath {
825 69     69 1 2319 my ($self,$xml,$ns)=@_;
826 69 50       211 $xml=$self->xml unless defined($xml);
827 69 50       176 $ns=$self->namespaces unless defined($ns);
828 69         208 my $p=XML::LibXML->new(clean_namespaces=>1);
829 69         4519 my $dom = $p->parse_string( $xml);
830 69         15574 my $x=XML::LibXML::XPathContext->new($dom);
831 69         140 while(my ($key,$value)=each %{$ns}) {
  276         778  
832 207         753 $x->registerNs($key,$value);
833             }
834 69         334 return $x;
835             }
836              
837             =head2 my $result=$self->validate;
838              
839             Returns a Data::Result Object. When true validation passed, when false it contains why validation failed.
840              
841             A better use case would be this:
842              
843             my $result=$self->validate;
844              
845             if($result) {
846             print "everything checks out\n";
847             } else {
848             foreach my $chunk (@{$result->get_data}) {
849             my ($nth,$signature,$digest)=@{$chunk}{qw(nth signature digest)};
850              
851             print "Results for processing chunk $nth\n";
852             print "Signature State: ".($signature ? "OK\n" : "Failed, error was $signature\n";
853             print "Digest State: ".($digest ? "OK\n" : "Failed, error was $digest\n";
854             }
855             }
856              
857             =cut
858              
859             sub validate {
860 18     18 1 75 my ($self)=@_;
861              
862 18         36 my $total=$self->build_xpath->findnodes($self->xpath_Signature)->size;
863              
864 18         983 my $list=[];
865 18         1045 my $result=Data::Result->new(data=>$list,is_true=>1);
866 18         2905 for(my $nth=1;$nth <= $total;++$nth) {
867 21         57 my $sig=$self->verify_signature(undef,$nth);
868 21         6427 my $digest=$self->verify_digest(undef,$nth);
869 21 50 33     5942 $result->is_true(0) unless $sig && $digest;
870 21         2826 my $ref={
871             nth=>$nth,
872             signature=>$sig,
873             digest=>$digest,
874             };
875 21         107 push @$list,$ref;
876             }
877 18 100       30 $result->is_true(0) if $#{$list}==-1;
  18         60  
878 18         222 return $result;
879              
880             }
881              
882             =head2 my $result=$self->verify_digest($nth)
883              
884             Returns a Data::Result object: when true, the signature was verified, when false it contains why it failed.
885              
886             =cut
887              
888             sub verify_digest {
889 22     22 1 104 my ($self,$x,$nth)=@_;
890              
891 22 100       69 $x=$self->build_xpath unless defined($x);
892              
893 22         69 my $result=$self->get_digest_value($x,$nth);
894 22 50       3948 return $result unless $result;
895 22         1045 my $value=$result->get_data;
896              
897 22         126 $result=$self->get_digest_method($x,$nth);
898 22 50       5698 return $result unless $result;
899 22         1043 my $method=$result->get_data;
900              
901 22         112 $result=$self->get_digest_node($x,$nth);
902 22 50       5759 return $result unless $result;
903 22         1025 my $node=$result->get_data;
904              
905 22         112 $result=$self->do_transforms($x,$node,$nth);
906 22 50       6909 return $result unless $result;
907 22         1018 my $xml=$result->get_data;
908              
909 22         140 my $cmp=$self->digest_cbs->{$method}->($self,$xml);
910 22         53 $cmp=~ s/\s+//sg;
911 22 50       50 return new_false Data::Result("orginal digest: $value ne $cmp") unless $value eq $cmp;
912              
913             # if we get here our digest checks out
914 22         64 return new_true Data::Result("Ok");
915             }
916              
917             =head2 my $result=$self->get_transforms($xpath_object,$nth)
918              
919             Returns a Data::Reslt object, when true it contains an array ref that contains each digest transform, when false it contains why it failed.
920              
921             Please note, the xpath generate is a concatination of $self->context($self->xpath_Transforms,$nth).$self->xpath_Transform, so keep that in mind when trying to change how transforms are looked up.
922              
923             =cut
924              
925              
926              
927             sub get_transforms {
928 23     23 1 743 my ($self,$x,$nth)=@_;
929              
930 23         94 my $xpath=$self->context($self->xpath_Transforms,$nth).$self->xpath_Transform;
931              
932 23         67 my $transforms=$x->findnodes($xpath);
933 23         1084 my @data=();
934 23         36 my @prefixes = ();
935              
936 23         55 my $pfx='';
937 23         58 foreach my $transform ($transforms->get_nodelist) {
938 46         252 my $algo = $x->findvalue($self->xpath_TransformAlgorithm, $transform);
939              
940 46 100 66     3186 if ($algo eq TRANSFORM_EXC_C14N or $algo eq TRANSFORM_EXC_C14N_COMMENTS)
941             {
942 17         63 my $rawprefixes = $x->findvalue($self->xpath_TransformInclusiveNamespacesPrefixList, $transform);
943              
944 17 100       973 if ($rawprefixes ne "")
945             {
946 1         7 @prefixes = split(' ', $rawprefixes);
947             }
948            
949 17 100       46 $pfx = $rawprefixes ? 'prefixes => \@prefixes' : '';
950             }
951              
952 46         1653 push @data, { algorithm => $algo, eval $pfx};
953             }
954              
955 23 50       109 return new_false Data::Result("Failed to find transforms in xpath: $xpath") unless @data>-1;
956 23         95 return new_true Data::Result(\@data);
957             }
958              
959             =head2 my $result=$self->get_digest_node($xpath_object)
960              
961             Returns a Data::Result Object, when true it contains the Digest Node, when false it contains why it failed.
962              
963             =cut
964              
965             sub get_digest_node {
966 23     23 1 664 my ($self,$x,$nth)=@_;
967 23         78 my ($id)=$x->findvalue($self->context($self->xpath_DigestId,$nth));
968 23 50       1645 return new_false Data::Result("Could not find our digest node id in xpath: ".$self->xpath_DigestId) unless defined($id);
969 23         76 my $next_xpath=$self->digest_id_convert_cb->($self,$x,$id);
970              
971 23         75 my ($node)=$x->findnodes($next_xpath);
972 23 50       1637 return new_false Data::Result("Could not find our digest node in xpath: $next_xpath") unless defined($node);
973              
974 23         110 return new_true Data::Result($node);
975             }
976              
977             =head2 my $result=$self->get_digest_method($xpath_object,$nth)
978              
979             Returns a Data::Result Object, when true it contains the Digest Method
980              
981             =cut
982              
983             sub get_digest_method {
984 23     23 1 1931 my ($self,$x,$nth)=@_;
985 23         55 my $xpath=$self->context($self->xpath_DigestMethod,$nth);
986 23         66 my ($digest_value)=$x->findvalue($xpath);
987 23 50       1792 return new_false Data::Result("Failed to find Digest Method in xpath: $xpath") unless defined($digest_value);
988 23 50       95 return new_false Data::Result("Unsupported Digest Method: $digest_value") unless exists $self->digest_cbs->{$digest_value};
989 23         68 return new_true Data::Result($digest_value);
990             }
991              
992             =head2 my $result=$self->get_digest_value($xpath_object,$nth)
993              
994             Returns a Data::Result Object, when true it contains the Digest Value.
995              
996             =cut
997              
998             sub get_digest_value {
999 23     23 1 689 my ($self,$x,$nth)=@_;
1000 23         69 my ($digest_value)=$x->findvalue($self->context($self->xpath_DigestValue,$nth));
1001 23 50       1746 return new_false Data::Result("Failed to find Digest Value in xpath: ".$self->xpath_DigestValue) unless defined($digest_value);
1002 23         367 $digest_value=~ s/\s+//sg;
1003 23         74 return new_true Data::Result($digest_value);
1004             }
1005              
1006             =head2 my $result=$self->verify_signature($nth);
1007              
1008             Returns a Data::Result Object, when true the signature was validated, when fails it contains why it failed.
1009              
1010             =cut
1011              
1012             sub verify_signature {
1013 22     22 1 95 my ($self,$x,$nth)=@_;
1014 22 100       66 $x=$self->build_xpath unless defined($x);
1015              
1016 22         82 my $pos=$self->context($self->xpath_Signature,$nth);
1017 22         64 my $x509_path=$pos.$self->xpath_x509Data;
1018 22         46 my $rsa_path=$pos.$self->xpath_RSAKeyValue;
1019 22         49 my $dsa_path=$pos.$self->xpath_DSAKeyValue;
1020 22 100       62 if(my $string=$x->findvalue($x509_path)) {
    100          
    50          
1021 17 50       1410 return new_false Data::Result("Found more than one x509 node in xpath: ".$self->xpath_x509Data) unless defined($string);
1022 17         43 return $self->verify_x509_sig($x,$string,$nth);
1023             } elsif($x->findvalue($rsa_path)) {
1024 4         553 return $self->verify_rsa($x,$string,$nth);
1025             } elsif($x->findvalue($dsa_path)) {
1026 1         193 return $self->verify_dsa($x,$string,$nth);
1027             } else {
1028 0         0 return new_false Data::Result("Currently Unsupported certificate method");
1029             }
1030             }
1031              
1032             =head2 my $result=$self->verify_dsa($x,$string,$nth)
1033              
1034             Returns a Data::Result object, when true it validated the DSA signature.
1035              
1036             =cut
1037              
1038             sub verify_dsa {
1039 1     1 1 4 my ($self,$x,$string,$nth)=@_;
1040              
1041 1         5 my $pos=$self->context($self->xpath_Signature,$nth);
1042 1         12 my $dsa_pub = Crypt::OpenSSL::DSA->new();
1043              
1044 1         3 foreach my $key (qw(p q g y)) {
1045 4         12 my $method="xpath_DSA_".uc($key);
1046 4         20 my $xpath=$pos.$self->$method();
1047 4         11 my $value=$x->findvalue($xpath);
1048              
1049 4 50       283 return new_false Data::Result("Did not find DSA $key in xpath: $xpath") unless defined($value);
1050 4         59 my $opt="set_$key";
1051 4         14 my $set=decode_base64(_trim($value));
1052 4 100       54 $dsa_pub->can($opt) ? $dsa_pub->$opt($set) : $dsa_pub->set_pub_key($set);
1053             }
1054              
1055 1         4 my $result=$self->tune_cert_and_get_sig($x,$nth,$dsa_pub);
1056 1         241 my $ref=$result->get_data;
1057             # DSA signatures are limited to a message body of 20 characters, so a sha1 digest is taken
1058 1 50       285 return new_true Data::Result("OK") if $dsa_pub->verify(sha1($ref->{xml}),$ref->{sig});
1059              
1060 0         0 return new_false Data::Result("Failed to validate DSA Signature");
1061             }
1062              
1063             =head2 my $xpath_string=$self->context($xpath,$nth)
1064              
1065             Returns an xpath wrapped in the nth instance syntax.
1066              
1067             Example
1068              
1069             my $xpath="//something"
1070             my $nth=2;
1071              
1072             my $xpath_string=$self->context($xpath,$nth);
1073              
1074             $xpath_string eq '(//something)[2]';
1075              
1076              
1077             Note: if nth is not set it defaults to 1
1078              
1079             =cut
1080              
1081             sub context {
1082 255     255 1 481 my ($self,$xpath,$nth)=@_;
1083 255 100       811 $nth=1 unless looks_like_number($nth);
1084 255         1000 return "($xpath)[$nth]";
1085             }
1086              
1087             =head2 my $result=$self->get_sig_canon($x,$nth)
1088              
1089             Returns a Data::Result object, when true it contains the canon xml of the $nth signature node.
1090              
1091             =cut
1092              
1093             sub get_sig_canon {
1094 22     22 1 40 my ($self,$x,$nth)=@_;
1095 22         48 my $result=$self->get_signed_info_node($x,$nth);
1096 22         3859 my $signed_info_node=$result->get_data;
1097 22 50       112 return $result unless $result;
1098              
1099 22         1032 return $self->do_canon($x,$signed_info_node,$nth);
1100             }
1101              
1102             =head2 my $result=$self->verify_x509_sig($x,$string,$nth)
1103              
1104             Returns a Data::Result Object, when true the x509 signature was validated.
1105              
1106             =cut
1107              
1108             sub verify_x509_sig {
1109 17     17 1 34 my ($self,$x,$string,$nth)=@_;
1110              
1111 17         38 my $x509=$self->clean_x509($string);
1112 17         1392 my $cert=Crypt::OpenSSL::X509->new_from_string($x509);
1113              
1114 17 100       77 if(defined($self->cacert)) {
1115 1         1120 my $ca=Crypt::OpenSSL::VerifyX509->new($self->cacert);
1116 1         4 my $result;
1117 1 50       2 eval {$result=new_false Data::Result("Could not verify the x509 cert against ".$self->cacert) unless $ca->verify($cert)};
  1         465  
1118 1 50       18 if($@) {
1119 0         0 return new_false Data::Result("Error using cert file: ".$self->cacert."error was: $@");
1120             }
1121 1 50       47 return $result if defined($result);
1122             }
1123              
1124 17         891 my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
1125              
1126 17         1580 my $result=$self->tune_cert_and_get_sig($x,$nth,$rsa_pub);
1127 17         4122 my $ref=$result->get_data;
1128              
1129             return Data::Result->new_false("x509 signature check failed, becase our generated signature did not match the one stored in the xml")
1130 17 50       1601 unless $rsa_pub->verify($ref->{xml},$ref->{sig});
1131              
1132 17         60 return new_true Data::Result("Ok");
1133             }
1134              
1135             =head2 my $result=$self->tune_cert_and_get_sig($x,$nth,$cert)
1136              
1137             Returns a Data::Result object, when true it contains the following hashref
1138              
1139             Structure:
1140              
1141             cert: the tuned cert
1142             sig: the binary signature to verify
1143             xml: the xml to be verified against the signature
1144              
1145             =cut
1146              
1147             sub tune_cert_and_get_sig {
1148 22     22 1 49 my ($self,$x,$nth,$cert)=@_;
1149              
1150 22         49 my $result=$self->get_signature_method($x,$nth,$cert);
1151 22 50       4075 return $result unless $result;
1152 22         1052 my $method=$result->get_data;
1153              
1154 22         125 $result=$self->tune_cert($cert,$method);
1155 22 50       5650 return $result unless $result;
1156              
1157 22         1072 $result=$self->get_sig_canon($x,$nth);
1158 22 50       9217 return $result unless $result;
1159 22         1011 my $xml=$result->get_data;
1160              
1161 22         114 $result=$self->get_signature_value($x,$nth);
1162 22 50       5717 return $result unless $result;
1163 22         1041 my $sig=$result->get_data;
1164              
1165 22         142 return new_true Data::Result({
1166             sig=>$sig,
1167             xml=>$xml,
1168             cert=>$cert,
1169             });
1170             }
1171              
1172             =head2 my $result=$self->verify_rsa($x,$nth)
1173              
1174             Returns a Data::Result Object, when true the the rsa key verification passed.
1175              
1176             =cut
1177              
1178             sub verify_rsa {
1179 4     4 1 12 my ($self,$x,$nth)=@_;
1180 4         11 my $pos=$self->context($self->xpath_Signature,$nth);
1181 4         16 my $xpath=$pos.$self->xpath_RSA_Modulus;
1182              
1183 4         10 my $mod=_trim($x->findvalue($xpath));
1184 4 50       16 return new_false Data::Result("Failed to find rsa modulus in xpath: $xpath") if $mod=~ m/^\s*$/s;
1185              
1186 4         175 $xpath=$pos.$self->xpath_RSA_Exponent;
1187 4         42 my $exp=_trim($x->findvalue($xpath));
1188 4 50       16 return new_false Data::Result("Failed to find rsa exponent in xpath: $xpath") if $exp=~ m/^\s*$/s;
1189              
1190 4         97 my $m = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($mod));
1191 4         17 my $e = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($exp));
1192              
1193 4         134 my $rsa_pub = Crypt::OpenSSL::RSA->new_key_from_parameters( $m, $e );
1194              
1195 4         1062 my $result=$self->tune_cert_and_get_sig($x,$nth,$rsa_pub);
1196 4         969 my $ref=$result->get_data;
1197              
1198             return Data::Result->new_false("rsa signature check failed, becase our generated signature did not match the one stored in the xml")
1199 4 50       892 unless $rsa_pub->verify($ref->{xml},$ref->{sig});
1200            
1201 4         16 return new_true Data::Result("Ok");
1202             }
1203              
1204             =head2 my $result=$self->do_transforms($xpath_object,$node_to_transform,$nth_node);
1205              
1206             Retruns a Data::Result Object, when true it contains the xml string of the context node.
1207              
1208             =cut
1209              
1210             sub do_transforms {
1211 22     22 1 47 my ($self,$x,$target,$nth)=@_;
1212 22         51 my $result=$self->get_transforms($x,$nth);
1213 22 50       4367 return $result unless $result;
1214 22         1062 my @todo=@{$result->get_data};
  22         48  
1215 22         106 my $xml;
1216 22         44 foreach my $transform (@todo) {
1217 44         2057 my $algorithm = $transform->{algorithm};
1218 44         86 my @prefixes = $transform->{prefixes};
1219 44         108 my $result=$self->transform($x,$target,$algorithm,$nth,@prefixes);
1220 44 50       15968 return $result unless $result;
1221 44         2137 $xml=$result->get_data;
1222             }
1223 22         2039 return new_true Data::Result($xml);
1224             }
1225              
1226             =head2 my $result=$self->do_canon($xpath_object,$node_to_transform,$nth_node);
1227              
1228             Returns a Data::Result Object, when true it contains the canonized string.
1229              
1230             =cut
1231              
1232             sub do_canon {
1233 22     22 1 52 my ($self,$x,$target,$nth)=@_;
1234 22         54 my $result=$self->get_canon($x,$nth);
1235 22 50       3844 return $result unless $result;
1236 22         1105 my $todo=$result->get_data;
1237 22         86 my $xml;
1238 22         29 foreach my $transform (@{$todo}) {
  22         64  
1239 22         64 my $result=$self->transform($x,$target,$transform,$nth,undef);
1240 22 50       7867 return $result unless $result;
1241 22         1090 $xml=$result->get_data;
1242             }
1243 22         2031 return new_true Data::Result($xml);
1244             }
1245              
1246             =head2 my $result=$self->get_canon($xpath_object,$nth)
1247              
1248             Returns a Data::Result Object, when true it contains an array ref of the canon methods.
1249              
1250             Special note, the xpath is generated as follows
1251              
1252             my $xpath=$self->context($self->xpath_SignedInfo,$nth).$self->xpath_CanonicalizationMethod;
1253              
1254             =cut
1255              
1256             sub get_canon {
1257 22     22 1 36 my ($self,$x,$nth)=@_;
1258              
1259 22         57 my $xpath=$self->context($self->xpath_SignedInfo,$nth).$self->xpath_CanonicalizationMethod;
1260 22         70 my $nodes=$x->find($xpath);
1261 22         1044 my $data=[];
1262 22         54 foreach my $att ($nodes->get_nodelist) {
1263 22         191 push @$data,$att->value;
1264             }
1265 22 50       35 return new_false Data::Result("No canonization methods found in xpath: $xpath") unless $#{$data} >-1;
  22         55  
1266 22         61 return new_true Data::Result($data);
1267             }
1268              
1269             =head2 my $result=$self->get_signature_value($xpath_object,$nth)
1270              
1271             Returns a Data::Result object, when true it contains the base64 decoded signature
1272              
1273             =cut
1274              
1275             sub get_signature_value {
1276 22     22 1 48 my ($self,$x,$nth)=@_;
1277 22         60 my ($encoded)=$x->findvalue($self->context($self->xpath_SignatureValue,$nth));
1278 22 50       1772 return new_false Data::Result("Signature Value was not found in xpath: ".$self->xpath_SignatureValue) unless defined($encoded);
1279              
1280 22         472 $encoded=~ s/\s+//sg;
1281 22         186 return new_true Data::Result(decode_base64($encoded));
1282             }
1283              
1284             =head2 my $result=$self->get_signed_info_node($xpath_object,$nth);
1285              
1286             Given $xpath_object, Returns a Data::Result when true it will contains the signed info node
1287              
1288             =cut
1289              
1290             sub get_signed_info_node {
1291 29     29 1 4928 my ($self,$x,$nth)=@_;
1292            
1293 29         90 my ($node)=$x->findnodes($self->context($self->xpath_SignedInfo,$nth));
1294 29 50       1249 return new_false Data::Result("Signature node(s) not found in xpath: ".$self->xpath_Signature) unless defined($node);
1295              
1296             # leave it up to our transform!
1297 29         87 return new_true Data::Result($node);
1298              
1299             }
1300              
1301             =head2 my $result=$self->get_signature_method($xpath_object,$nth_node,$cert|undef)
1302              
1303             Returns a Data::Result object, when true it contains the SignatureMethod. If $cert is passed in, it will cert the hashing mode for the cert
1304              
1305             =cut
1306              
1307             sub get_signature_method {
1308 22     22 1 39 my ($self,$x,$nth,$cert)=@_;
1309              
1310 22         65 my ($method_url)=$x->findvalue($self->context($self->xpath_SignatureMethod,$nth));
1311 22 50       1701 return new_false Data::Result("SignatureMethod not found in xpath: ".$self->xpath_SignatureMethod) unless defined($method_url);
1312              
1313 22         75 return new_true Data::Result($method_url);
1314             }
1315              
1316             =head2 my $result=$self->tune_cert($cert,$method)
1317              
1318             Returns a Data::Result Object, when true Sets the hashing method for the $cert object.
1319              
1320             =cut
1321              
1322             sub tune_cert {
1323 27     27 1 55 my ($self,$cert,$method)=@_;
1324 27 50       94 return new_false Data::Result("Unsupported hashing method: $method") unless exists $self->tune_cert_cbs->{$method};
1325              
1326 27         90 $self->tune_cert_cbs->{$method}->($self,$cert);
1327 27         70 return new_true Data::Result;
1328             }
1329              
1330             =head2 my $x509=$self->clean_x509($string)
1331              
1332             Converts a given string to an x509 certificate.
1333              
1334             =cut
1335              
1336             sub clean_x509 {
1337 17     17 1 32 my ($self,$cert)=@_;
1338 17         355 $cert =~ s/\s+//g;
1339 17         23 my @lines;
1340 17         151 while (length $cert > 64) {
1341 285         1714 push @lines, substr $cert, 0, 64, '';
1342             }
1343 17         37 push @lines,$cert;
1344 17         106 $cert = join "\n", @lines;
1345 17         63 $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "\n-----END CERTIFICATE-----\n";
1346 17         73 return $cert;
1347             }
1348              
1349             =head2 my $result=$self->transform($xpath_object,$node,$transformType,$nth,$ec14n_inclusive_prefixes)
1350              
1351             Given the $node XML::LibXML::Element and $transformType, returns a Data::Result object. When true the call to $result->get_data will return the xml, when false it will contain a string that shows why it failed.
1352              
1353             =cut
1354              
1355             sub transform {
1356 79     79 1 1380 my ($self,$x,$node,$type,$nth,$ec14n_inclusive_prefixes)=@_;
1357 79 50       278 return new_false Data::Result("tansform of [$type] is not supported") unless exists $self->mutate_cbs->{$type};
1358 79         228 return new_true Data::Result($self->mutate_cbs->{$type}->($self,$x,$node,$nth,$ec14n_inclusive_prefixes));
1359             }
1360              
1361             =head2 my $array_ref=$self->transforms
1362              
1363             Returns an ArrayRef that contains the list of transform methods we will use when signing the xml.
1364              
1365             This list is built out of the following:
1366              
1367             0: $self->envelope_method
1368             1: $self->canon_method
1369              
1370             =cut
1371              
1372             sub transforms {
1373 12     12 1 24 my ($self)=@_;
1374 12         50 return [$self->envelope_method,$self->canon_method];
1375             }
1376              
1377             =head2 my $xml=$self->create_digest_xml($id,$digest)
1378              
1379             Produces a text xml fragment to be used for an xml digest.
1380              
1381             =cut
1382              
1383             sub create_digest_xml {
1384 6     6 1 13 my ($self,$id,$digest)=@_;
1385 6         13 my $method=$self->digest_method;
1386 6         8 my @list;
1387 6         12 my $ns=$self->tag_namespace;
1388 6         14 my $transforms=$self->transforms;
1389 6         10 foreach my $transform (@{$transforms}) {
  6         13  
1390 12         38 push @list,
1391             qq{ <${ns}:Transform Algorithm="$transform" />};
1392             }
1393 6         20 $transforms=join "\n",@list;
1394 6         135 return qq{<${ns}:Reference URI="#$id">
1395             <${ns}:Transforms>\n$transforms
1396             </${ns}:Transforms>
1397             <${ns}:DigestMethod Algorithm="$method" />
1398             <${ns}:DigestValue>$digest</${ns}:DigestValue>
1399             </${ns}:Reference>};
1400             }
1401              
1402             =head2 my $xml=$self->create_signedinfo_xml($digest_xml)
1403              
1404             Produces text xml fragment to be used for an xml signature
1405              
1406             =cut
1407              
1408             sub create_signedinfo_xml {
1409 6     6 1 13 my ($self,$digest_xml) = @_;
1410 6         14 my $method=$self->signature_method;
1411 6         9 my $canon_method=$self->canon_method;
1412 6         14 my $xmlns=$self->create_xmlns;
1413 6         12 my $ns=$self->tag_namespace;
1414 6         45 return qq{<${ns}:SignedInfo $xmlns>
1415             <${ns}:CanonicalizationMethod Algorithm="$canon_method" />
1416             <${ns}:SignatureMethod Algorithm="$method" />
1417             $digest_xml
1418             </${ns}:SignedInfo>};
1419             }
1420              
1421             =head2 my $xmlns=$self->create_xmlns
1422              
1423             Creates our common xmlns string based on our namespaces.
1424              
1425             =cut
1426              
1427             sub create_xmlns {
1428 12     12 1 18 my ($self)=@_;
1429 12         19 my @list;
1430 12         18 foreach my $key (sort keys %{$self->namespaces}) {
  12         67  
1431 36         72 my $value=$self->namespaces->{$key};
1432 36         87 push @list,qq{xmlns:${key}="$value"};
1433             }
1434              
1435 12         35 my $xmlns=join ' ',@list;
1436 12         27 return $xmlns;
1437             }
1438              
1439             =head2 my $xml=$self->create_signature_xml
1440              
1441             Creates the signature xml for signing.
1442              
1443             =cut
1444              
1445             sub create_signature_xml {
1446 6     6 1 21 my ($self,$signed_info,$signature_value,$key_string)=@_;
1447 6         13 my $xmlns=$self->create_xmlns;
1448 6         19 my $ns=$self->tag_namespace;
1449 6         69 return qq{<${ns}:Signature $xmlns>
1450             $signed_info
1451             <${ns}:SignatureValue>$signature_value</${ns}:SignatureValue>
1452             $key_string
1453             </${ns}:Signature>};
1454             }
1455              
1456             =head2 my $result=$self->load_cert_from_file($filename)
1457              
1458             Returns a Data::Result structure, when true it contains a hasref with the following elements:
1459              
1460             type: 'dsa|rsa|x509'
1461             cert: $cert_object
1462              
1463             =cut
1464              
1465             sub load_cert_from_file {
1466 6     6 1 15 my ($self,$file)=@_;
1467 6 50       13 return new_false Data::Result("file is not defined") unless defined($file);
1468 6 50       205 return new_false Data::Result("cannot read: $file") unless -r $file;
1469              
1470 6         54 my $io=IO::File->new($file,'r');
1471 6 50       716 return new_false Data::Result("Cannot open $file, error was $!") unless $io;
1472 6         158 my $text=join '',$io->getlines;
1473 6         3055 return $self->detect_cert($text);
1474             }
1475              
1476             =head2 my $result=$self->detect_cert($text)
1477              
1478             Returns a Data::Result object, when true it contains the following hashref
1479              
1480             type: 'dsa|rsa|x509'
1481             cert: $cert_object
1482              
1483             =cut
1484              
1485             sub detect_cert {
1486 6     6 1 22 my ($self,$text)=@_;
1487 6 100       61 if ($text =~ m/BEGIN ([DR]SA) PRIVATE KEY/s ) {
    50          
    50          
1488              
1489 5 100       22 if($1 eq 'RSA') {
1490 4         13 return $self->load_rsa_string($text);
1491             } else {
1492 1         9 return $self->load_dsa_string($text);
1493             }
1494              
1495             } elsif ( $text =~ m/BEGIN PRIVATE KEY/ ) {
1496 0         0 return $self->load_rsa_string($text);
1497             } elsif ($text =~ m/BEGIN CERTIFICATE/) {
1498 1         4 return $self->load_x509_string($text);
1499             } else {
1500 0         0 return new_false Data::Result("Unsupported key type");
1501             }
1502             }
1503              
1504             =head2 my $result=$self->load_rsa_string($string)
1505              
1506             Returns a Data::Result object, when true it contains the following hashref:
1507              
1508             type: 'rsa'
1509             cert: $cert_object
1510              
1511             =cut
1512              
1513             sub load_rsa_string {
1514 4     4 1 8 my ($self,$str)=@_;
1515 4         216 my $rsaKey = Crypt::OpenSSL::RSA->new_private_key( $str );
1516 4 50       22 return new_false Data::Result("Failed to parse rsa key") unless $rsaKey;
1517 4         16 $rsaKey->use_pkcs1_padding();
1518 4         30 return new_true Data::Result({cert=>$rsaKey,type=>'rsa'});
1519             }
1520              
1521             =head2 my $result=$self->load_x509_string($string)
1522              
1523             Returns a Data::Result object, when true it contains the following hashref:
1524              
1525             type: 'x509'
1526             cert: $cert_object
1527              
1528             =cut
1529              
1530             sub load_x509_string {
1531 1     1 1 4 my ($self,$str)=@_;
1532 1         92 my $x509Key = Crypt::OpenSSL::X509->new_from_string( $str );
1533 1 50       17 return new_false Data::Result("Failed to parse x509 cert") unless $x509Key;
1534 1         10 return new_true Data::Result({cert=>$x509Key,type=>'x509'});
1535             }
1536              
1537             =head2 my $result=$self->load_dsa_string($string)
1538              
1539             Returns a Data::Result object, when true it contains the following hashref:
1540              
1541             type: 'dsa'
1542             cert: $cert_object
1543              
1544             =cut
1545              
1546             sub load_dsa_string {
1547 1     1 1 5 my ($self,$str)=@_;
1548 1         13 my $dsa_key = Crypt::OpenSSL::DSA->read_priv_key_str( $str );
1549 1 50       89 return new_false("Failed to parse dsa key") unless $dsa_key;
1550 1         10 return new_true Data::Result({cert=>$dsa_key,type=>'dsa'});
1551             }
1552              
1553             =head2 my $result=$self->get_xml_to_sign($xpath_object,$nth)
1554              
1555             Returns a Data::Result object, when true it contains the xml object to sign.
1556              
1557             =cut
1558              
1559             sub get_xml_to_sign {
1560 7     7 1 24 my ($self,$x,$nth)=@_;
1561 7         25 my $xpath=$self->context($self->xpath_ToSign,$nth);
1562 7         22 my ($node)=$x->findnodes($xpath);
1563              
1564 7 50       306 return new_false Data::Result("Failed to find xml to sign in xpath: $xpath") unless defined($node);
1565 7         23 return new_true Data::Result($node);
1566             }
1567              
1568             =head2 my $result=$self->get_signer_id($xpath_object,$nth)
1569              
1570             Returns a Data::Result object, when true it contains the id value
1571              
1572             =cut
1573              
1574             sub get_signer_id {
1575 6     6 1 13 my ($self,$x,$nth)=@_;
1576 6         28 my $xpath=$self->context($self->xpath_IdValue,$nth);
1577 6         19 my ($node)=$x->findvalue($xpath);
1578 6 50       469 return new_false Data::Result("Failed to find id value in xpath: $xpath") unless defined($node);
1579 6         30 return new_true Data::Result($node);
1580             }
1581              
1582             =head2 my $result=$self->sign
1583              
1584             Returns a Data::Result Object, when true it contains the signed xml string.
1585              
1586             =cut
1587              
1588             sub sign {
1589 5     5 1 1586 my ($self)=@_;
1590 5         11 my $x=$self->build_xpath;
1591              
1592 5 50       131 return new_false Data::Result("sign_cert object is not defined") unless defined($self->sign_cert);
1593              
1594 5         46 my $total=$x->findnodes($self->xpath_ToSign)->size;
1595 5 50       294 return new_false Data::Result("No xml found to sign") if $total==0;
1596 5         90 foreach(my $nth=1;$nth <=$total;++$nth) {
1597 6         142 my $result=$self->sign_chunk($x,$nth);
1598 6 50       1852 return $result unless $result;
1599             }
1600 5         643 my ($root)=$x->findnodes($self->xpath_Root);
1601              
1602 5         203 return new_true Data::Result($root->toString);
1603             }
1604              
1605             =head2 my $result=$self->sign_chunk($xpath_object,$nth)
1606              
1607             Returns a Data::Result object, when true, the nth element with //@ID was signed and updated in $xpath_object. This method provides absolute granular control over what node is signed.
1608              
1609             =cut
1610              
1611             sub sign_chunk {
1612 6     6 1 13 my ($self,$x,$nth)=@_;
1613              
1614 6         14 my $result=$self->get_xml_to_sign($x,$nth);
1615 6 50       1114 return $result unless $result;
1616 6         283 my $node_to_sign=$result->get_data;
1617              
1618 6         34 $result=$self->get_signer_id($x,$nth);
1619 6 50       1581 return $result unless $result;
1620 6         297 my $id=$result->get_data;
1621              
1622 6         47 my $digest_canon=$self->mutate_cbs->{$self->canon_method}->($self,$x,$node_to_sign,$nth,undef);
1623 6         457 my $digest=$self->digest_cbs->{$self->digest_method}->($self,$digest_canon);
1624              
1625 6         21 my $digest_xml = $self->create_digest_xml( $id,$digest );
1626 6         16 my $signedinfo_xml = $self->create_signedinfo_xml($digest_xml);
1627 6         21 my $p= XML::LibXML->new();
1628              
1629             # fun note, we have to append the child to get it to canonize correctly
1630 6         87 my $signed_info=$p->parse_balanced_chunk($signedinfo_xml);
1631 6         1403 $node_to_sign->appendChild($signed_info);
1632 6         32 $result=$self->get_signed_info_node($x,$nth);
1633 6 50       1614 return $result unless $result;
1634 6         292 $signed_info=$result->get_data;
1635              
1636 6         37 my $canon;
1637 6         13 foreach my $method (@{$self->transforms}) {
  6         17  
1638 12         50 $result=$self->transform($x,$signed_info,$method,$nth,undef);
1639 12 50       4000 return $result unless $result;
1640 12         557 $canon=$result->get_data;
1641             }
1642              
1643             # now we need to remove the child to contnue on
1644 6         97 $node_to_sign->removeChild($signed_info);
1645              
1646 6         21 my $sig;
1647 6         182 my $cert=$self->sign_cert;
1648 6 100       150 if ($self->key_type eq 'dsa') {
    50          
1649             # DSA only permits the signing of 20 bytes or less, hence the sha1
1650 1         279 my $raw= $cert->sign( sha1($canon) );
1651 1         5 $sig=encode_base64( $raw, "\n" );
1652             } elsif($self->key_type eq 'rsa') {
1653 5         148 my $result=$self->tune_cert($cert,$self->signature_method);
1654 5 50       836 return $result unless $result;
1655 5         61760 my $raw= $cert->sign( $canon );
1656 5         208 $sig=encode_base64( $raw, "\n" );
1657             }
1658 6         488 my $method="create_".$self->key_type."_xml";
1659 6         59 my $key_xml=$self->$method($cert);
1660 6         577 my $signed_xml=$self->create_signature_xml($signed_info->toString,$sig,$key_xml);
1661 6         20 my $signed_frag=$p->parse_balanced_chunk($signed_xml);
1662 6         1744 $node_to_sign->appendChild($signed_frag);
1663 6         32 return new_true Data::Result("OK");
1664             }
1665              
1666             =head2 my $xml=$self->create_x509_xml($cert)
1667              
1668             Creates the xml from the Certificate Object.
1669              
1670             =cut
1671              
1672             sub create_x509_xml {
1673 0     0 1 0 my ($self,$cert)=@_;
1674 0         0 my $cert_text = $cert->as_string;
1675 0         0 return $self->build_x509_xml($cert_text);
1676             }
1677              
1678             =head2 my $xml=$self->build_x509_xml($encoded_key)
1679              
1680             Given the base64 encoded key, create a block of x509 xml.
1681              
1682             =cut
1683              
1684             sub build_x509_xml {
1685 1     1 1 4 my ($self,$cert_text)=@_;
1686 1         5 my $ns=$self->tag_namespace;
1687 1         9 $cert_text =~ s/-----[^-]*-----//gm;
1688 1         8 return "<${ns}:KeyInfo><${ns}:X509Data><${ns}:X509Certificate>\n"._trim($cert_text)."\n</${ns}:X509Certificate></${ns}:X509Data></${ns}:KeyInfo>";
1689             }
1690              
1691             =head2 my $result=$self->find_key_cert
1692              
1693             Returns a Data::Result Object, when true it contains the x509 cert xml.
1694              
1695             =cut
1696              
1697             sub find_key_cert {
1698 5     5 1 11 my ($self)=@_;
1699 5 100       82 if(defined(my $file=$self->cert_file)) {
    50          
1700 1         13 my $result=$self->load_cert_from_file($file);
1701 1 50       270 if($result) {
1702 1         69 my $str=_trim($result->get_data->{cert}->as_string);
1703 1         10 return new_true Data::Result($self->build_x509_xml($str));
1704             } else {
1705 0         0 return $result;
1706             }
1707             } elsif(defined($self->cert_string)) {
1708 0         0 return new_true Data::Result($self->build_x509_xml(_trim($self->cert_string)));
1709             }
1710              
1711 4         61 return new_false Data::Result("no cert found");
1712             }
1713              
1714             =head2 my $xml=$self->create_rsa_xml($cert)
1715              
1716             Creates the xml from the Certificate Object.
1717              
1718             =cut
1719              
1720             sub create_rsa_xml {
1721 5     5 1 13 my ($self,$rsaKey)=@_;
1722              
1723 5         17 my $result=$self->find_key_cert;
1724 5 100       1144 return $result->get_data if $result;
1725              
1726 4         268 my $bigNum = ( $rsaKey->get_key_parameters() )[1];
1727 4         1002 my $bin = $bigNum->to_bin();
1728 4         16 my $exp = encode_base64( $bin, '' );
1729 4         98 $bigNum = ( $rsaKey->get_key_parameters() )[0];
1730 4         121 $bin = $bigNum->to_bin();
1731 4         19 my $mod = encode_base64( $bin, '' );
1732 4         11 my $ns=$self->tag_namespace;
1733              
1734 4         100 return "<${ns}:KeyInfo>
1735             <${ns}:KeyValue>
1736             <${ns}:RSAKeyValue>
1737             <${ns}:Modulus>$mod</${ns}:Modulus>
1738             <${ns}:Exponent>$exp</${ns}:Exponent>
1739             </${ns}:RSAKeyValue>
1740             </${ns}:KeyValue>
1741             </${ns}:KeyInfo>";
1742             }
1743              
1744             =head2 my $xml=$self->create_dsa_xml($cert)
1745              
1746             Creates the xml for the Key Object.
1747              
1748             =cut
1749              
1750             sub create_dsa_xml {
1751 1     1 1 10 my ($self,$dsa_key)=@_;
1752              
1753 1         8 my $g=encode_base64( $dsa_key->get_g(), '' );
1754 1         7 my $p=encode_base64( $dsa_key->get_p(), '' );
1755 1         8 my $q=encode_base64( $dsa_key->get_q(), '' );
1756 1         10 my $y=encode_base64( $dsa_key->get_pub_key(), '' );
1757              
1758 1         5 my $ns=$self->tag_namespace;
1759 1         16 return "<${ns}:KeyInfo>
1760             <${ns}:KeyValue>
1761             <${ns}:DSAKeyValue>
1762             <${ns}:P>$p</${ns}:P>
1763             <${ns}:Q>$q</${ns}:Q>
1764             <${ns}:G>$g</${ns}:G>
1765             <${ns}:Y>$y</${ns}:Y>
1766             </${ns}:DSAKeyValue>
1767             </${ns}:KeyValue>
1768             </${ns}:KeyInfo>";
1769             }
1770              
1771             sub _trim {
1772 14     14   614 my ($str)=@_;
1773 14         832 $str=~ s/(?:^\s+|\s+$)//sg;
1774 14         68 return $str;
1775             }
1776              
1777             =head1 Limitations
1778              
1779             This package currently has some limitations.
1780              
1781             =head2 Supported Key Types and formats for signing/validation
1782              
1783             Currently this module only supports RSA and DSA keys in pem format.
1784              
1785             =head2 CaCert Validation
1786              
1787             Currently CaCert validation only works with RSA keys.
1788              
1789             =head1 Credits
1790              
1791             This code is based on the following modules: L<XML::Sig>, L<Net::SAML2::XML::Sig>, L<Authen::NZRealMe::XMLSig>, and L<Mojo::XMLSig> and would not exist today withot them.
1792              
1793             =head1 Bugs
1794              
1795             Currently there are no known bugs, but if any are found please report them on our github project. Patches and pull requests are welcomed!
1796              
1797             L<https://github.com/akalinux/xml-sig-oo>
1798              
1799             =head1 Author
1800              
1801             AKALINUX <AKALINUX@CPAN.ORG>
1802              
1803             =cut
1804              
1805             1;