File Coverage

blib/lib/XML/Sig/OO.pm
Criterion Covered Total %
statement 168 465 36.1
branch 30 170 17.6
condition 4 15 26.6
subroutine 36 71 50.7
pod 42 43 97.6
total 280 764 36.6


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