File Coverage

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


line stmt bran cond sub pod time code
1             package XML::Sig::OO;
2              
3             our $VERSION="0.008";
4              
5 1     1   1071 use Modern::Perl;
  1         2  
  1         8  
6 1     1   819 use Moo;
  1         7383  
  1         5  
7 1     1   2079 use MooX::Types::MooseLike::Base qw(:all);
  1         9131  
  1         339  
8 1     1   8 use MIME::Base64;
  1         10  
  1         59  
9 1     1   737 use XML::LibXML;
  1         42791  
  1         6  
10 1     1   159 use XML::LibXML::XPathContext;
  1         3  
  1         21  
11 1     1   529 use Crypt::OpenSSL::X509;
  1         13585  
  1         162  
12 1     1   698 use Crypt::OpenSSL::RSA;
  1         5324  
  1         72  
13 1     1   12 use Crypt::OpenSSL::Bignum;
  1         3  
  1         26  
14 1     1   526 use Crypt::OpenSSL::DSA;
  1         876  
  1         52  
15 1     1   487 use Crypt::OpenSSL::VerifyX509;
  1         697  
  1         38  
16 1     1   990 use Digest::SHA qw(sha1);
  1         3576  
  1         122  
17 1     1   585 use Ref::Util qw( is_plain_hashref);
  1         1826  
  1         81  
18 1     1   501 use Data::Result;
  1         56506  
  1         53  
19 1     1   12 use Carp qw(croak);
  1         17  
  1         77  
20 1     1   7 use Scalar::Util qw(looks_like_number);
  1         22  
  1         55  
21 1     1   6 use namespace::clean;
  1         3  
  1         9  
22 1     1   1345 use constant TRANSFORM_EXC_C14N => 'http://www.w3.org/2001/10/xml-exc-c14n#';
  1         3  
  1         93  
23 1     1   7 use constant TRANSFORM_EXC_C14N_COMMENTS => 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments';
  1         2  
  1         9157  
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   100 my ($self,$algo, $content) = @_;
251 28         1042 my $digest = Digest::SHA->can("${algo}_base64")->($content);
252 28         121 while (length($digest) % 4) { $digest .= '=' }
  28         110  
253 28         102 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   72 my ($self,$cert,$alg)=@_;
283              
284 27         91 my $method="use_${alg}_hash";
285              
286 27 100       204 if($cert->can($method)) {
287 26         142 $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   15 my ($method,$comment)=@_;
317             return sub {
318 57     57   200 my ($self,$x,$node,$nth,$ec14n_inclusive_prefixes)=@_;
319              
320 57 100       208 if ($method eq "toStringEC14N")
321             {
322 32         208 return $node->$method($comment, undef, $ec14n_inclusive_prefixes);
323             }
324             else
325             {
326 25         161 return $node->$method($comment);
327             }
328 6         50 };
329             }
330              
331             sub _envelope_transform {
332 28     28   90 my ($self,$x,$node,$nth)=@_;
333              
334 28         132 my $xpath=$self->context($self->xpath_Signature,$nth);
335 28         115 my ($target)=$x->findnodes($xpath,$node);
336 28 100       1678 $node->removeChild($target) if defined($target);
337 28         260 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   72 my ($self,$xpath_object,$id)=@_;
530 23         183 $id=~ s/^#//s;
531 23         110 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 20928 my ($self)=@_;
799              
800             # sanity check dsa signature method
801 25 50 33     497 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       446 croak "namespaces does not contain: ".$self->tag_namespace unless exists $self->namespaces->{$self->tag_namespace};
806 25 50       159 croak $self->signature_method." is an unsupported signature method" unless exists $self->tune_cert_cbs->{$self->signature_method};
807 25 100 66     510 if(defined($self->key_file) && !defined($self->sign_cert)) {
808 5         84 my $result=$self->load_cert_from_file($self->key_file);
809 5 50       1268 croak $result unless $result;
810 5         293 my ($key_type,$cert)=@{$result->get_data}{qw(type cert)};
  5         21  
811 5         121 $self->sign_cert($cert);
812 5         331 $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 2806 my ($self,$xml,$ns)=@_;
826 69 50       355 $xml=$self->xml unless defined($xml);
827 69 50       335 $ns=$self->namespaces unless defined($ns);
828 69         493 my $p=XML::LibXML->new(clean_namespaces=>1);
829 69         6194 my $dom = $p->parse_string( $xml);
830 69         22054 my $x=XML::LibXML::XPathContext->new($dom);
831 69         192 while(my ($key,$value)=each %{$ns}) {
  276         923  
832 207         1021 $x->registerNs($key,$value);
833             }
834 69         457 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 131 my ($self)=@_;
861              
862 18         70 my $total=$self->build_xpath->findnodes($self->xpath_Signature)->size;
863              
864 18         1933 my $list=[];
865 18         1285 my $result=Data::Result->new(data=>$list,is_true=>1);
866 18         3682 for(my $nth=1;$nth <= $total;++$nth) {
867 21         120 my $sig=$self->verify_signature(undef,$nth);
868 21         7141 my $digest=$self->verify_digest(undef,$nth);
869 21 50 33     6249 $result->is_true(0) unless $sig && $digest;
870 21         2650 my $ref={
871             nth=>$nth,
872             signature=>$sig,
873             digest=>$digest,
874             };
875 21         155 push @$list,$ref;
876             }
877 18 100       40 $result->is_true(0) if $#{$list}==-1;
  18         92  
878 18         250 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 145 my ($self,$x,$nth)=@_;
890              
891 22 100       149 $x=$self->build_xpath unless defined($x);
892              
893 22         121 my $result=$self->get_digest_value($x,$nth);
894 22 50       4178 return $result unless $result;
895 22         1175 my $value=$result->get_data;
896              
897 22         193 $result=$self->get_digest_method($x,$nth);
898 22 50       5944 return $result unless $result;
899 22         1154 my $method=$result->get_data;
900              
901 22         171 $result=$self->get_digest_node($x,$nth);
902 22 50       5814 return $result unless $result;
903 22         1100 my $node=$result->get_data;
904              
905 22         229 $result=$self->do_transforms($x,$node,$nth);
906 22 50       7013 return $result unless $result;
907 22         1080 my $xml=$result->get_data;
908              
909 22         234 my $cmp=$self->digest_cbs->{$method}->($self,$xml);
910 22         103 $cmp=~ s/\s+//sg;
911 22 50       107 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         87 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             sub get_transforms {
926 23     23 1 721 my ($self,$x,$nth)=@_;
927              
928 23         177 my $xpath=$self->context($self->xpath_Transforms,$nth).$self->xpath_Transform;
929              
930 23         86 my $transforms=$x->findnodes($xpath);
931 23         1248 my $data=[];
932              
933 23         97 foreach my $transform ($transforms->get_nodelist) {
934 46         300 my $algo = $x->findvalue($self->xpath_TransformAlgorithm, $transform);
935              
936 46         3245 my $prefixes = [];
937 46         81 my $pfx=[];
938 46 100 66     291 if ($algo eq TRANSFORM_EXC_C14N or $algo eq TRANSFORM_EXC_C14N_COMMENTS) {
939 17         81 my $rawprefixes = $x->findvalue($self->xpath_TransformInclusiveNamespacesPrefixList, $transform);
940              
941 17 100       950 if ($rawprefixes ne "") {
942 1         5 @$prefixes = split(' ', $rawprefixes);
943             }
944 17 100       56 $pfx = $rawprefixes ? [prefixes => $prefixes] : [ ] ;
945             }
946              
947 46         223 push @$data, { algorithm => $algo, @$pfx };
948             }
949              
950 23 50       50 return new_false Data::Result("Failed to find transforms in xpath: $xpath") unless $#{$data}>-1;
  23         99  
951 23         84 return new_true Data::Result($data);
952             }
953              
954             =head2 my $result=$self->get_digest_node($xpath_object)
955              
956             Returns a Data::Result Object, when true it contains the Digest Node, when false it contains why it failed.
957              
958             =cut
959              
960             sub get_digest_node {
961 23     23 1 700 my ($self,$x,$nth)=@_;
962 23         121 my ($id)=$x->findvalue($self->context($self->xpath_DigestId,$nth));
963 23 50       1738 return new_false Data::Result("Could not find our digest node id in xpath: ".$self->xpath_DigestId) unless defined($id);
964 23         140 my $next_xpath=$self->digest_id_convert_cb->($self,$x,$id);
965              
966 23         108 my ($node)=$x->findnodes($next_xpath);
967 23 50       1763 return new_false Data::Result("Could not find our digest node in xpath: $next_xpath") unless defined($node);
968              
969 23         104 return new_true Data::Result($node);
970             }
971              
972             =head2 my $result=$self->get_digest_method($xpath_object,$nth)
973              
974             Returns a Data::Result Object, when true it contains the Digest Method
975              
976             =cut
977              
978             sub get_digest_method {
979 23     23 1 2068 my ($self,$x,$nth)=@_;
980 23         110 my $xpath=$self->context($self->xpath_DigestMethod,$nth);
981 23         84 my ($digest_value)=$x->findvalue($xpath);
982 23 50       1923 return new_false Data::Result("Failed to find Digest Method in xpath: $xpath") unless defined($digest_value);
983 23 50       177 return new_false Data::Result("Unsupported Digest Method: $digest_value") unless exists $self->digest_cbs->{$digest_value};
984 23         95 return new_true Data::Result($digest_value);
985             }
986              
987             =head2 my $result=$self->get_digest_value($xpath_object,$nth)
988              
989             Returns a Data::Result Object, when true it contains the Digest Value.
990              
991             =cut
992              
993             sub get_digest_value {
994 23     23 1 881 my ($self,$x,$nth)=@_;
995 23         134 my ($digest_value)=$x->findvalue($self->context($self->xpath_DigestValue,$nth));
996 23 50       2010 return new_false Data::Result("Failed to find Digest Value in xpath: ".$self->xpath_DigestValue) unless defined($digest_value);
997 23         450 $digest_value=~ s/\s+//sg;
998 23         101 return new_true Data::Result($digest_value);
999             }
1000              
1001             =head2 my $result=$self->verify_signature($nth);
1002              
1003             Returns a Data::Result Object, when true the signature was validated, when fails it contains why it failed.
1004              
1005             =cut
1006              
1007             sub verify_signature {
1008 22     22 1 139 my ($self,$x,$nth)=@_;
1009 22 100       101 $x=$self->build_xpath unless defined($x);
1010              
1011 22         128 my $pos=$self->context($self->xpath_Signature,$nth);
1012 22         94 my $x509_path=$pos.$self->xpath_x509Data;
1013 22         93 my $rsa_path=$pos.$self->xpath_RSAKeyValue;
1014 22         87 my $dsa_path=$pos.$self->xpath_DSAKeyValue;
1015 22 100       121 if(my $string=$x->findvalue($x509_path)) {
    100          
    50          
1016 17 50       1874 return new_false Data::Result("Found more than one x509 node in xpath: ".$self->xpath_x509Data) unless defined($string);
1017 17         89 return $self->verify_x509_sig($x,$string,$nth);
1018             } elsif($x->findvalue($rsa_path)) {
1019 4         648 return $self->verify_rsa($x,$string,$nth);
1020             } elsif($x->findvalue($dsa_path)) {
1021 1         218 return $self->verify_dsa($x,$string,$nth);
1022             } else {
1023 0         0 return new_false Data::Result("Currently Unsupported certificate method");
1024             }
1025             }
1026              
1027             =head2 my $result=$self->verify_dsa($x,$string,$nth)
1028              
1029             Returns a Data::Result object, when true it validated the DSA signature.
1030              
1031             =cut
1032              
1033             sub verify_dsa {
1034 1     1 1 4 my ($self,$x,$string,$nth)=@_;
1035              
1036 1         6 my $pos=$self->context($self->xpath_Signature,$nth);
1037 1         9 my $dsa_pub = Crypt::OpenSSL::DSA->new();
1038              
1039 1         4 foreach my $key (qw(p q g y)) {
1040 4         13 my $method="xpath_DSA_".uc($key);
1041 4         18 my $xpath=$pos.$self->$method();
1042 4         12 my $value=$x->findvalue($xpath);
1043              
1044 4 50       274 return new_false Data::Result("Did not find DSA $key in xpath: $xpath") unless defined($value);
1045 4         58 my $opt="set_$key";
1046 4         11 my $set=decode_base64(_trim($value));
1047 4 100       47 $dsa_pub->can($opt) ? $dsa_pub->$opt($set) : $dsa_pub->set_pub_key($set);
1048             }
1049              
1050 1         7 my $result=$self->tune_cert_and_get_sig($x,$nth,$dsa_pub);
1051 1         270 my $ref=$result->get_data;
1052             # DSA signatures are limited to a message body of 20 characters, so a sha1 digest is taken
1053 1 50       294 return new_true Data::Result("OK") if $dsa_pub->verify(sha1($ref->{xml}),$ref->{sig});
1054              
1055 0         0 return new_false Data::Result("Failed to validate DSA Signature");
1056             }
1057              
1058             =head2 my $xpath_string=$self->context($xpath,$nth)
1059              
1060             Returns an xpath wrapped in the nth instance syntax.
1061              
1062             Example
1063              
1064             my $xpath="//something"
1065             my $nth=2;
1066              
1067             my $xpath_string=$self->context($xpath,$nth);
1068              
1069             $xpath_string eq '(//something)[2]';
1070              
1071              
1072             Note: if nth is not set it defaults to 1
1073              
1074             =cut
1075              
1076             sub context {
1077 255     255 1 660 my ($self,$xpath,$nth)=@_;
1078 255 100       955 $nth=1 unless looks_like_number($nth);
1079 255         1256 return "($xpath)[$nth]";
1080             }
1081              
1082             =head2 my $result=$self->get_sig_canon($x,$nth)
1083              
1084             Returns a Data::Result object, when true it contains the canon xml of the $nth signature node.
1085              
1086             =cut
1087              
1088             sub get_sig_canon {
1089 22     22 1 61 my ($self,$x,$nth)=@_;
1090 22         88 my $result=$self->get_signed_info_node($x,$nth);
1091 22         3957 my $signed_info_node=$result->get_data;
1092 22 50       130 return $result unless $result;
1093              
1094 22         1115 return $self->do_canon($x,$signed_info_node,$nth);
1095             }
1096              
1097             =head2 my $result=$self->verify_x509_sig($x,$string,$nth)
1098              
1099             Returns a Data::Result Object, when true the x509 signature was validated.
1100              
1101             =cut
1102              
1103             sub verify_x509_sig {
1104 17     17 1 68 my ($self,$x,$string,$nth)=@_;
1105              
1106 17         73 my $x509=$self->clean_x509($string);
1107 17         1988 my $cert=Crypt::OpenSSL::X509->new_from_string($x509);
1108              
1109 17 100       119 if(defined($self->cacert)) {
1110 1         408 my $ca=Crypt::OpenSSL::VerifyX509->new($self->cacert);
1111 1         4 my $result;
1112 1 50       3 eval {$result=new_false Data::Result("Could not verify the x509 cert against ".$self->cacert) unless $ca->verify($cert)};
  1         529  
1113 1 50       6 if($@) {
1114 0         0 return new_false Data::Result("Error using cert file: ".$self->cacert."error was: $@");
1115             }
1116 1 50       45 return $result if defined($result);
1117             }
1118              
1119 17         1284 my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
1120              
1121 17         1411 my $result=$self->tune_cert_and_get_sig($x,$nth,$rsa_pub);
1122 17         4189 my $ref=$result->get_data;
1123              
1124             return Data::Result->new_false("x509 signature check failed, becase our generated signature did not match the one stored in the xml")
1125 17 50       2168 unless $rsa_pub->verify($ref->{xml},$ref->{sig});
1126              
1127 17         78 return new_true Data::Result("Ok");
1128             }
1129              
1130             =head2 my $result=$self->tune_cert_and_get_sig($x,$nth,$cert)
1131              
1132             Returns a Data::Result object, when true it contains the following hashref
1133              
1134             Structure:
1135              
1136             cert: the tuned cert
1137             sig: the binary signature to verify
1138             xml: the xml to be verified against the signature
1139              
1140             =cut
1141              
1142             sub tune_cert_and_get_sig {
1143 22     22 1 90 my ($self,$x,$nth,$cert)=@_;
1144              
1145 22         114 my $result=$self->get_signature_method($x,$nth,$cert);
1146 22 50       4353 return $result unless $result;
1147 22         1265 my $method=$result->get_data;
1148              
1149 22         177 $result=$self->tune_cert($cert,$method);
1150 22 50       6100 return $result unless $result;
1151              
1152 22         1103 $result=$self->get_sig_canon($x,$nth);
1153 22 50       9104 return $result unless $result;
1154 22         1079 my $xml=$result->get_data;
1155              
1156 22         159 $result=$self->get_signature_value($x,$nth);
1157 22 50       5867 return $result unless $result;
1158 22         1092 my $sig=$result->get_data;
1159              
1160 22         210 return new_true Data::Result({
1161             sig=>$sig,
1162             xml=>$xml,
1163             cert=>$cert,
1164             });
1165             }
1166              
1167             =head2 my $result=$self->verify_rsa($x,$nth)
1168              
1169             Returns a Data::Result Object, when true the the rsa key verification passed.
1170              
1171             =cut
1172              
1173             sub verify_rsa {
1174 4     4 1 17 my ($self,$x,$nth)=@_;
1175 4         19 my $pos=$self->context($self->xpath_Signature,$nth);
1176 4         22 my $xpath=$pos.$self->xpath_RSA_Modulus;
1177              
1178 4         17 my $mod=_trim($x->findvalue($xpath));
1179 4 50       19 return new_false Data::Result("Failed to find rsa modulus in xpath: $xpath") if $mod=~ m/^\s*$/s;
1180              
1181 4         184 $xpath=$pos.$self->xpath_RSA_Exponent;
1182 4         43 my $exp=_trim($x->findvalue($xpath));
1183 4 50       16 return new_false Data::Result("Failed to find rsa exponent in xpath: $xpath") if $exp=~ m/^\s*$/s;
1184              
1185 4         122 my $m = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($mod));
1186 4         22 my $e = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($exp));
1187              
1188 4         122 my $rsa_pub = Crypt::OpenSSL::RSA->new_key_from_parameters( $m, $e );
1189              
1190 4         706 my $result=$self->tune_cert_and_get_sig($x,$nth,$rsa_pub);
1191 4         1059 my $ref=$result->get_data;
1192              
1193             return Data::Result->new_false("rsa signature check failed, becase our generated signature did not match the one stored in the xml")
1194 4 50       1020 unless $rsa_pub->verify($ref->{xml},$ref->{sig});
1195            
1196 4         21 return new_true Data::Result("Ok");
1197             }
1198              
1199             =head2 my $result=$self->do_transforms($xpath_object,$node_to_transform,$nth_node);
1200              
1201             Retruns a Data::Result Object, when true it contains the xml string of the context node.
1202              
1203             =cut
1204              
1205             sub do_transforms {
1206 22     22 1 76 my ($self,$x,$target,$nth)=@_;
1207 22         85 my $result=$self->get_transforms($x,$nth);
1208 22 50       4482 return $result unless $result;
1209 22         1096 my @todo=@{$result->get_data};
  22         57  
1210 22         124 my $xml;
1211 22         83 foreach my $transform (@todo) {
1212 44         2140 my $algorithm = $transform->{algorithm};
1213 44         99 my @prefixes = $transform->{prefixes};
1214 44         163 my $result=$self->transform($x,$target,$algorithm,$nth,@prefixes);
1215 44 50       17273 return $result unless $result;
1216 44         2186 $xml=$result->get_data;
1217             }
1218 22         2215 return new_true Data::Result($xml);
1219             }
1220              
1221             =head2 my $result=$self->do_canon($xpath_object,$node_to_transform,$nth_node);
1222              
1223             Returns a Data::Result Object, when true it contains the canonized string.
1224              
1225             =cut
1226              
1227             sub do_canon {
1228 22     22 1 88 my ($self,$x,$target,$nth)=@_;
1229 22         110 my $result=$self->get_canon($x,$nth);
1230 22 50       3882 return $result unless $result;
1231 22         1069 my $todo=$result->get_data;
1232 22         100 my $xml;
1233 22         38 foreach my $transform (@{$todo}) {
  22         86  
1234 22         91 my $result=$self->transform($x,$target,$transform,$nth,undef);
1235 22 50       8927 return $result unless $result;
1236 22         1070 $xml=$result->get_data;
1237             }
1238 22         2062 return new_true Data::Result($xml);
1239             }
1240              
1241             =head2 my $result=$self->get_canon($xpath_object,$nth)
1242              
1243             Returns a Data::Result Object, when true it contains an array ref of the canon methods.
1244              
1245             Special note, the xpath is generated as follows
1246              
1247             my $xpath=$self->context($self->xpath_SignedInfo,$nth).$self->xpath_CanonicalizationMethod;
1248              
1249             =cut
1250              
1251             sub get_canon {
1252 22     22 1 64 my ($self,$x,$nth)=@_;
1253              
1254 22         81 my $xpath=$self->context($self->xpath_SignedInfo,$nth).$self->xpath_CanonicalizationMethod;
1255 22         80 my $nodes=$x->find($xpath);
1256 22         1114 my $data=[];
1257 22         97 foreach my $att ($nodes->get_nodelist) {
1258 22         286 push @$data,$att->value;
1259             }
1260 22 50       46 return new_false Data::Result("No canonization methods found in xpath: $xpath") unless $#{$data} >-1;
  22         96  
1261 22         77 return new_true Data::Result($data);
1262             }
1263              
1264             =head2 my $result=$self->get_signature_value($xpath_object,$nth)
1265              
1266             Returns a Data::Result object, when true it contains the base64 decoded signature
1267              
1268             =cut
1269              
1270             sub get_signature_value {
1271 22     22 1 78 my ($self,$x,$nth)=@_;
1272 22         120 my ($encoded)=$x->findvalue($self->context($self->xpath_SignatureValue,$nth));
1273 22 50       2291 return new_false Data::Result("Signature Value was not found in xpath: ".$self->xpath_SignatureValue) unless defined($encoded);
1274              
1275 22         563 $encoded=~ s/\s+//sg;
1276 22         291 return new_true Data::Result(decode_base64($encoded));
1277             }
1278              
1279             =head2 my $result=$self->get_signed_info_node($xpath_object,$nth);
1280              
1281             Given $xpath_object, Returns a Data::Result when true it will contains the signed info node
1282              
1283             =cut
1284              
1285             sub get_signed_info_node {
1286 29     29 1 5960 my ($self,$x,$nth)=@_;
1287            
1288 29         155 my ($node)=$x->findnodes($self->context($self->xpath_SignedInfo,$nth));
1289 29 50       1420 return new_false Data::Result("Signature node(s) not found in xpath: ".$self->xpath_Signature) unless defined($node);
1290              
1291             # leave it up to our transform!
1292 29         113 return new_true Data::Result($node);
1293              
1294             }
1295              
1296             =head2 my $result=$self->get_signature_method($xpath_object,$nth_node,$cert|undef)
1297              
1298             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
1299              
1300             =cut
1301              
1302             sub get_signature_method {
1303 22     22 1 89 my ($self,$x,$nth,$cert)=@_;
1304              
1305 22         103 my ($method_url)=$x->findvalue($self->context($self->xpath_SignatureMethod,$nth));
1306 22 50       1866 return new_false Data::Result("SignatureMethod not found in xpath: ".$self->xpath_SignatureMethod) unless defined($method_url);
1307              
1308 22         126 return new_true Data::Result($method_url);
1309             }
1310              
1311             =head2 my $result=$self->tune_cert($cert,$method)
1312              
1313             Returns a Data::Result Object, when true Sets the hashing method for the $cert object.
1314              
1315             =cut
1316              
1317             sub tune_cert {
1318 27     27 1 79 my ($self,$cert,$method)=@_;
1319 27 50       171 return new_false Data::Result("Unsupported hashing method: $method") unless exists $self->tune_cert_cbs->{$method};
1320              
1321 27         152 $self->tune_cert_cbs->{$method}->($self,$cert);
1322 27         95 return new_true Data::Result;
1323             }
1324              
1325             =head2 my $x509=$self->clean_x509($string)
1326              
1327             Converts a given string to an x509 certificate.
1328              
1329             =cut
1330              
1331             sub clean_x509 {
1332 17     17 1 55 my ($self,$cert)=@_;
1333 17         473 $cert =~ s/\s+//g;
1334 17         32 my @lines;
1335 17         201 while (length $cert > 64) {
1336 285         1813 push @lines, substr $cert, 0, 64, '';
1337             }
1338 17         41 push @lines,$cert;
1339 17         125 $cert = join "\n", @lines;
1340 17         83 $cert = "-----BEGIN CERTIFICATE-----\n" . $cert . "\n-----END CERTIFICATE-----\n";
1341 17         89 return $cert;
1342             }
1343              
1344             =head2 my $result=$self->transform($xpath_object,$node,$transformType,$nth,$ec14n_inclusive_prefixes)
1345              
1346             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.
1347              
1348             =cut
1349              
1350             sub transform {
1351 79     79 1 1655 my ($self,$x,$node,$type,$nth,$ec14n_inclusive_prefixes)=@_;
1352 79 50       424 return new_false Data::Result("tansform of [$type] is not supported") unless exists $self->mutate_cbs->{$type};
1353 79         490 return new_true Data::Result($self->mutate_cbs->{$type}->($self,$x,$node,$nth,$ec14n_inclusive_prefixes));
1354             }
1355              
1356             =head2 my $array_ref=$self->transforms
1357              
1358             Returns an ArrayRef that contains the list of transform methods we will use when signing the xml.
1359              
1360             This list is built out of the following:
1361              
1362             0: $self->envelope_method
1363             1: $self->canon_method
1364              
1365             =cut
1366              
1367             sub transforms {
1368 12     12 1 28 my ($self)=@_;
1369 12         65 return [$self->envelope_method,$self->canon_method];
1370             }
1371              
1372             =head2 my $xml=$self->create_digest_xml($id,$digest)
1373              
1374             Produces a text xml fragment to be used for an xml digest.
1375              
1376             =cut
1377              
1378             sub create_digest_xml {
1379 6     6 1 16 my ($self,$id,$digest)=@_;
1380 6         14 my $method=$self->digest_method;
1381 6         10 my @list;
1382 6         24 my $ns=$self->tag_namespace;
1383 6         24 my $transforms=$self->transforms;
1384 6         16 foreach my $transform (@{$transforms}) {
  6         21  
1385 12         50 push @list,
1386             qq{ <${ns}:Transform Algorithm="$transform" />};
1387             }
1388 6         24 $transforms=join "\n",@list;
1389 6         98 return qq{<${ns}:Reference URI="#$id">
1390             <${ns}:Transforms>\n$transforms
1391             </${ns}:Transforms>
1392             <${ns}:DigestMethod Algorithm="$method" />
1393             <${ns}:DigestValue>$digest</${ns}:DigestValue>
1394             </${ns}:Reference>};
1395             }
1396              
1397             =head2 my $xml=$self->create_signedinfo_xml($digest_xml)
1398              
1399             Produces text xml fragment to be used for an xml signature
1400              
1401             =cut
1402              
1403             sub create_signedinfo_xml {
1404 6     6 1 14 my ($self,$digest_xml) = @_;
1405 6         27 my $method=$self->signature_method;
1406 6         19 my $canon_method=$self->canon_method;
1407 6         18 my $xmlns=$self->create_xmlns;
1408 6         16 my $ns=$self->tag_namespace;
1409 6         62 return qq{<${ns}:SignedInfo $xmlns>
1410             <${ns}:CanonicalizationMethod Algorithm="$canon_method" />
1411             <${ns}:SignatureMethod Algorithm="$method" />
1412             $digest_xml
1413             </${ns}:SignedInfo>};
1414             }
1415              
1416             =head2 my $xmlns=$self->create_xmlns
1417              
1418             Creates our common xmlns string based on our namespaces.
1419              
1420             =cut
1421              
1422             sub create_xmlns {
1423 12     12 1 29 my ($self)=@_;
1424 12         25 my @list;
1425 12         21 foreach my $key (sort keys %{$self->namespaces}) {
  12         139  
1426 36         81 my $value=$self->namespaces->{$key};
1427 36         117 push @list,qq{xmlns:${key}="$value"};
1428             }
1429              
1430 12         51 my $xmlns=join ' ',@list;
1431 12         34 return $xmlns;
1432             }
1433              
1434             =head2 my $xml=$self->create_signature_xml
1435              
1436             Creates the signature xml for signing.
1437              
1438             =cut
1439              
1440             sub create_signature_xml {
1441 6     6 1 32 my ($self,$signed_info,$signature_value,$key_string)=@_;
1442 6         26 my $xmlns=$self->create_xmlns;
1443 6         25 my $ns=$self->tag_namespace;
1444 6         94 return qq{<${ns}:Signature $xmlns>
1445             $signed_info
1446             <${ns}:SignatureValue>$signature_value</${ns}:SignatureValue>
1447             $key_string
1448             </${ns}:Signature>};
1449             }
1450              
1451             =head2 my $result=$self->load_cert_from_file($filename)
1452              
1453             Returns a Data::Result structure, when true it contains a hasref with the following elements:
1454              
1455             type: 'dsa|rsa|x509'
1456             cert: $cert_object
1457              
1458             =cut
1459              
1460             sub load_cert_from_file {
1461 6     6 1 22 my ($self,$file)=@_;
1462 6 50       30 return new_false Data::Result("file is not defined") unless defined($file);
1463 6 50       290 return new_false Data::Result("cannot read: $file") unless -r $file;
1464              
1465 6         83 my $io=IO::File->new($file,'r');
1466 6 50       1043 return new_false Data::Result("Cannot open $file, error was $!") unless $io;
1467 6         188 my $text=join '',$io->getlines;
1468 6         709 return $self->detect_cert($text);
1469             }
1470              
1471             =head2 my $result=$self->detect_cert($text)
1472              
1473             Returns a Data::Result object, when true it contains the following hashref
1474              
1475             type: 'dsa|rsa|x509'
1476             cert: $cert_object
1477              
1478             =cut
1479              
1480             sub detect_cert {
1481 6     6 1 22 my ($self,$text)=@_;
1482 6 100       62 if ($text =~ m/BEGIN ([DR]SA) PRIVATE KEY/s ) {
    50          
    50          
1483              
1484 5 100       29 if($1 eq 'RSA') {
1485 4         18 return $self->load_rsa_string($text);
1486             } else {
1487 1         6 return $self->load_dsa_string($text);
1488             }
1489              
1490             } elsif ( $text =~ m/BEGIN PRIVATE KEY/ ) {
1491 0         0 return $self->load_rsa_string($text);
1492             } elsif ($text =~ m/BEGIN CERTIFICATE/) {
1493 1         8 return $self->load_x509_string($text);
1494             } else {
1495 0         0 return new_false Data::Result("Unsupported key type");
1496             }
1497             }
1498              
1499             =head2 my $result=$self->load_rsa_string($string)
1500              
1501             Returns a Data::Result object, when true it contains the following hashref:
1502              
1503             type: 'rsa'
1504             cert: $cert_object
1505              
1506             =cut
1507              
1508             sub load_rsa_string {
1509 4     4 1 16 my ($self,$str)=@_;
1510 4         361 my $rsaKey = Crypt::OpenSSL::RSA->new_private_key( $str );
1511 4 50       23 return new_false Data::Result("Failed to parse rsa key") unless $rsaKey;
1512 4         26 $rsaKey->use_pkcs1_padding();
1513 4         46 return new_true Data::Result({cert=>$rsaKey,type=>'rsa'});
1514             }
1515              
1516             =head2 my $result=$self->load_x509_string($string)
1517              
1518             Returns a Data::Result object, when true it contains the following hashref:
1519              
1520             type: 'x509'
1521             cert: $cert_object
1522              
1523             =cut
1524              
1525             sub load_x509_string {
1526 1     1 1 4 my ($self,$str)=@_;
1527 1         128 my $x509Key = Crypt::OpenSSL::X509->new_from_string( $str );
1528 1 50       19 return new_false Data::Result("Failed to parse x509 cert") unless $x509Key;
1529 1         15 return new_true Data::Result({cert=>$x509Key,type=>'x509'});
1530             }
1531              
1532             =head2 my $result=$self->load_dsa_string($string)
1533              
1534             Returns a Data::Result object, when true it contains the following hashref:
1535              
1536             type: 'dsa'
1537             cert: $cert_object
1538              
1539             =cut
1540              
1541             sub load_dsa_string {
1542 1     1 1 4 my ($self,$str)=@_;
1543 1         13 my $dsa_key = Crypt::OpenSSL::DSA->read_priv_key_str( $str );
1544 1 50       150 return new_false("Failed to parse dsa key") unless $dsa_key;
1545 1         14 return new_true Data::Result({cert=>$dsa_key,type=>'dsa'});
1546             }
1547              
1548             =head2 my $result=$self->get_xml_to_sign($xpath_object,$nth)
1549              
1550             Returns a Data::Result object, when true it contains the xml object to sign.
1551              
1552             =cut
1553              
1554             sub get_xml_to_sign {
1555 7     7 1 24 my ($self,$x,$nth)=@_;
1556 7         43 my $xpath=$self->context($self->xpath_ToSign,$nth);
1557 7         35 my ($node)=$x->findnodes($xpath);
1558              
1559 7 50       371 return new_false Data::Result("Failed to find xml to sign in xpath: $xpath") unless defined($node);
1560 7         28 return new_true Data::Result($node);
1561             }
1562              
1563             =head2 my $result=$self->get_signer_id($xpath_object,$nth)
1564              
1565             Returns a Data::Result object, when true it contains the id value
1566              
1567             =cut
1568              
1569             sub get_signer_id {
1570 6     6 1 19 my ($self,$x,$nth)=@_;
1571 6         45 my $xpath=$self->context($self->xpath_IdValue,$nth);
1572 6         31 my ($node)=$x->findvalue($xpath);
1573 6 50       593 return new_false Data::Result("Failed to find id value in xpath: $xpath") unless defined($node);
1574 6         24 return new_true Data::Result($node);
1575             }
1576              
1577             =head2 my $result=$self->sign
1578              
1579             Returns a Data::Result Object, when true it contains the signed xml string.
1580              
1581             =cut
1582              
1583             sub sign {
1584 5     5 1 1843 my ($self)=@_;
1585 5         20 my $x=$self->build_xpath;
1586              
1587 5 50       129 return new_false Data::Result("sign_cert object is not defined") unless defined($self->sign_cert);
1588              
1589 5         72 my $total=$x->findnodes($self->xpath_ToSign)->size;
1590 5 50       458 return new_false Data::Result("No xml found to sign") if $total==0;
1591 5         144 foreach(my $nth=1;$nth <=$total;++$nth) {
1592 6         152 my $result=$self->sign_chunk($x,$nth);
1593 6 50       1959 return $result unless $result;
1594             }
1595 5         700 my ($root)=$x->findnodes($self->xpath_Root);
1596              
1597 5         329 return new_true Data::Result($root->toString);
1598             }
1599              
1600             =head2 my $result=$self->sign_chunk($xpath_object,$nth)
1601              
1602             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.
1603              
1604             =cut
1605              
1606             sub sign_chunk {
1607 6     6 1 22 my ($self,$x,$nth)=@_;
1608              
1609 6         26 my $result=$self->get_xml_to_sign($x,$nth);
1610 6 50       1191 return $result unless $result;
1611 6         302 my $node_to_sign=$result->get_data;
1612              
1613 6         47 $result=$self->get_signer_id($x,$nth);
1614 6 50       1775 return $result unless $result;
1615 6         296 my $id=$result->get_data;
1616              
1617 6         75 my $digest_canon=$self->mutate_cbs->{$self->canon_method}->($self,$x,$node_to_sign,$nth,undef);
1618 6         580 my $digest=$self->digest_cbs->{$self->digest_method}->($self,$digest_canon);
1619              
1620 6         30 my $digest_xml = $self->create_digest_xml( $id,$digest );
1621 6         28 my $signedinfo_xml = $self->create_signedinfo_xml($digest_xml);
1622 6         27 my $p= XML::LibXML->new();
1623              
1624             # fun note, we have to append the child to get it to canonize correctly
1625 6         108 my $signed_info=$p->parse_balanced_chunk($signedinfo_xml);
1626 6         1544 $node_to_sign->appendChild($signed_info);
1627 6         45 $result=$self->get_signed_info_node($x,$nth);
1628 6 50       1763 return $result unless $result;
1629 6         327 $signed_info=$result->get_data;
1630              
1631 6         47 my $canon;
1632 6         13 foreach my $method (@{$self->transforms}) {
  6         15  
1633 12         57 $result=$self->transform($x,$signed_info,$method,$nth,undef);
1634 12 50       4057 return $result unless $result;
1635 12         577 $canon=$result->get_data;
1636             }
1637              
1638             # now we need to remove the child to contnue on
1639 6         103 $node_to_sign->removeChild($signed_info);
1640              
1641 6         30 my $sig;
1642 6         316 my $cert=$self->sign_cert;
1643 6 100       209 if ($self->key_type eq 'dsa') {
    50          
1644             # DSA only permits the signing of 20 bytes or less, hence the sha1
1645 1         296 my $raw= $cert->sign( sha1($canon) );
1646 1         8 $sig=encode_base64( $raw, "\n" );
1647             } elsif($self->key_type eq 'rsa') {
1648 5         169 my $result=$self->tune_cert($cert,$self->signature_method);
1649 5 50       857 return $result unless $result;
1650 5         62645 my $raw= $cert->sign( $canon );
1651 5         483 $sig=encode_base64( $raw, "\n" );
1652             }
1653 6         719 my $method="create_".$self->key_type."_xml";
1654 6         110 my $key_xml=$self->$method($cert);
1655 6         774 my $signed_xml=$self->create_signature_xml($signed_info->toString,$sig,$key_xml);
1656 6         34 my $signed_frag=$p->parse_balanced_chunk($signed_xml);
1657 6         2219 $node_to_sign->appendChild($signed_frag);
1658 6         46 return new_true Data::Result("OK");
1659             }
1660              
1661             =head2 my $xml=$self->create_x509_xml($cert)
1662              
1663             Creates the xml from the Certificate Object.
1664              
1665             =cut
1666              
1667             sub create_x509_xml {
1668 0     0 1 0 my ($self,$cert)=@_;
1669 0         0 my $cert_text = $cert->as_string;
1670 0         0 return $self->build_x509_xml($cert_text);
1671             }
1672              
1673             =head2 my $xml=$self->build_x509_xml($encoded_key)
1674              
1675             Given the base64 encoded key, create a block of x509 xml.
1676              
1677             =cut
1678              
1679             sub build_x509_xml {
1680 1     1 1 4 my ($self,$cert_text)=@_;
1681 1         6 my $ns=$self->tag_namespace;
1682 1         12 $cert_text =~ s/-----[^-]*-----//gm;
1683 1         6 return "<${ns}:KeyInfo><${ns}:X509Data><${ns}:X509Certificate>\n"._trim($cert_text)."\n</${ns}:X509Certificate></${ns}:X509Data></${ns}:KeyInfo>";
1684             }
1685              
1686             =head2 my $result=$self->find_key_cert
1687              
1688             Returns a Data::Result Object, when true it contains the x509 cert xml.
1689              
1690             =cut
1691              
1692             sub find_key_cert {
1693 5     5 1 18 my ($self)=@_;
1694 5 100       96 if(defined(my $file=$self->cert_file)) {
    50          
1695 1         16 my $result=$self->load_cert_from_file($file);
1696 1 50       283 if($result) {
1697 1         63 my $str=_trim($result->get_data->{cert}->as_string);
1698 1         7 return new_true Data::Result($self->build_x509_xml($str));
1699             } else {
1700 0         0 return $result;
1701             }
1702             } elsif(defined($self->cert_string)) {
1703 0         0 return new_true Data::Result($self->build_x509_xml(_trim($self->cert_string)));
1704             }
1705              
1706 4         77 return new_false Data::Result("no cert found");
1707             }
1708              
1709             =head2 my $xml=$self->create_rsa_xml($cert)
1710              
1711             Creates the xml from the Certificate Object.
1712              
1713             =cut
1714              
1715             sub create_rsa_xml {
1716 5     5 1 19 my ($self,$rsaKey)=@_;
1717              
1718 5         25 my $result=$self->find_key_cert;
1719 5 100       1437 return $result->get_data if $result;
1720              
1721 4         319 my $bigNum = ( $rsaKey->get_key_parameters() )[1];
1722 4         684 my $bin = $bigNum->to_bin();
1723 4         17 my $exp = encode_base64( $bin, '' );
1724 4         102 $bigNum = ( $rsaKey->get_key_parameters() )[0];
1725 4         124 $bin = $bigNum->to_bin();
1726 4         18 my $mod = encode_base64( $bin, '' );
1727 4         20 my $ns=$self->tag_namespace;
1728              
1729 4         111 return "<${ns}:KeyInfo>
1730             <${ns}:KeyValue>
1731             <${ns}:RSAKeyValue>
1732             <${ns}:Modulus>$mod</${ns}:Modulus>
1733             <${ns}:Exponent>$exp</${ns}:Exponent>
1734             </${ns}:RSAKeyValue>
1735             </${ns}:KeyValue>
1736             </${ns}:KeyInfo>";
1737             }
1738              
1739             =head2 my $xml=$self->create_dsa_xml($cert)
1740              
1741             Creates the xml for the Key Object.
1742              
1743             =cut
1744              
1745             sub create_dsa_xml {
1746 1     1 1 4 my ($self,$dsa_key)=@_;
1747              
1748 1         9 my $g=encode_base64( $dsa_key->get_g(), '' );
1749 1         8 my $p=encode_base64( $dsa_key->get_p(), '' );
1750 1         7 my $q=encode_base64( $dsa_key->get_q(), '' );
1751 1         7 my $y=encode_base64( $dsa_key->get_pub_key(), '' );
1752              
1753 1         4 my $ns=$self->tag_namespace;
1754 1         16 return "<${ns}:KeyInfo>
1755             <${ns}:KeyValue>
1756             <${ns}:DSAKeyValue>
1757             <${ns}:P>$p</${ns}:P>
1758             <${ns}:Q>$q</${ns}:Q>
1759             <${ns}:G>$g</${ns}:G>
1760             <${ns}:Y>$y</${ns}:Y>
1761             </${ns}:DSAKeyValue>
1762             </${ns}:KeyValue>
1763             </${ns}:KeyInfo>";
1764             }
1765              
1766             sub _trim {
1767 14     14   610 my ($str)=@_;
1768 14         880 $str=~ s/(?:^\s+|\s+$)//sg;
1769 14         58 return $str;
1770             }
1771              
1772             =head1 Limitations
1773              
1774             This package currently has some limitations.
1775              
1776             =head2 Supported Key Types and formats for signing/validation
1777              
1778             Currently this module only supports RSA and DSA keys in pem format.
1779              
1780             =head2 CaCert Validation
1781              
1782             Currently CaCert validation only works with RSA keys.
1783              
1784             =head1 Credits
1785              
1786             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.
1787              
1788             =head1 Bugs
1789              
1790             Currently there are no known bugs, but if any are found please report them on our github project. Patches and pull requests are welcomed!
1791              
1792             L<https://github.com/akalinux/xml-sig-oo>
1793              
1794             =head1 Author
1795              
1796             AKALINUX <AKALINUX@CPAN.ORG>
1797              
1798             =cut
1799              
1800             1;