File Coverage

blib/lib/XML/Sig/OO.pm
Criterion Covered Total %
statement 434 444 97.7
branch 97 162 59.8
condition 4 9 44.4
subroutine 66 67 98.5
pod 42 43 97.6
total 643 725 88.6


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