File Coverage

lib/XML/Compile/WSS/SignedInfo.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # Copyrights 2012-2016 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5 1     1   3 use warnings;
  1         1  
  1         25  
6 1     1   3 use strict;
  1         1  
  1         23  
7              
8             package XML::Compile::WSS::SignedInfo;
9 1     1   3 use vars '$VERSION';
  1         1  
  1         43  
10             $VERSION = '2.02';
11              
12              
13 1     1   3 use Log::Report 'xml-compile-wss-sig';
  1         0  
  1         4  
14              
15 1     1   639 use Digest::SHA ();
  1         1911  
  1         23  
16 1     1   38 use XML::Compile::C14N;
  0            
  0            
17             use XML::Compile::Util qw/type_of_node/;
18             use XML::Compile::WSS::Util qw/:wss11 :dsig/;
19             use XML::Compile::C14N::Util qw/:c14n is_canon_constant/;
20              
21             # Quite some problems to get canonicalization compatible between
22             # client and server. Especially where some xmlns's are optional.
23             # It may help to enforce some namespaces via $wsdl->prefixFor($ns)
24             my @default_canon_ns = qw(SOAP-ENV); # qw/wsu/;
25              
26             # There can only be one c14n rule active, because it would otherwise
27             # produce a prefix
28             my $c14n;
29              
30              
31             sub new(@) { my $class = shift; (bless {}, $class)->init({@_}) }
32             sub init($)
33             { my ($self, $args) = @_;
34             $self->{XCWS_pref} = $args->{prefix_list} || \@default_canon_ns;
35             my $wss = $args->{wss};
36              
37             # Immediately try-out the configured digest method.
38             my $digest = $self->{XCWS_dig}
39             = $args->{digest_method} || DSIG_SHA1;
40             try { $self->_get_digester($digest, undef) };
41             error __x"digest method {name} is not useable: {err}"
42             , name => $digest, err => $@
43             if $@;
44              
45             my $canon = $self->{XCWS_can}
46             = $args->{canon_method} || C14N_EXC_NO_COMM;
47              
48             $self->{XCWS_c14n} = $args->{c14n} ||= $c14n
49             ||= XML::Compile::C14N->new(for => $canon, schema => $wss->schema);
50              
51             $self;
52             }
53              
54              
55             sub fromConfig(@)
56             { my $class = shift;
57             $class->new(@_==1 ? %{$_[0]} : @_);
58             }
59              
60             #-----------------
61              
62             sub defaultDigestMethod() { shift->{XCWS_dig} }
63             sub defaultCanonMethod() { shift->{XCWS_can} }
64             sub defaultPrefixList() { shift->{XCWS_pref} }
65             sub c14n() { shift->{XCWS_c14n} }
66              
67             #-----------------
68              
69             sub builder($%)
70             { my ($self, $wss, %args) = @_;
71              
72             my $schema = $wss->schema;
73             my $digest = $args{digest_method} || $self->defaultDigestMethod;
74             my $canon = $args{canon_method} || $self->defaultCanonMethod;
75             my $preflist = $args{prefix_list} || $self->defaultPrefixList;
76              
77             my $canonic = $self->_get_canonic($canon, $preflist);
78             $schema->prefixFor($canon); # enforce inclusion of c14n namespace
79              
80             my $digester = $self->_get_digester($digest, $canonic);
81             my $cleanup = $self->_get_repair_xml($wss);
82              
83             my $infow = $schema->writer('ds:SignedInfo');
84             my $inclw = $self->_canon_incl($wss);
85              
86             sub {
87             my ($doc, $elems, $sign_method) = @_;
88              
89             # warn "SIGN ELEMS @$elems";
90             my @refs;
91             foreach (@$elems)
92             { my $node = $cleanup->($_, @$preflist);
93             my $value = $digester->($node);
94              
95             my $transform =
96             +{ Algorithm => $canon
97             , cho_any => [ +{$inclw->($doc, $preflist)} ]
98             };
99              
100             my $id = $node->getAttribute('Id') # for the Signatures
101             || $node->getAttributeNS(WSU_NS, 'Id'); # or else
102              
103             push @refs,
104             +{ URI => '#'.$id
105             , ds_Transforms => { ds_Transform => [$transform] }
106             , ds_DigestValue => $value
107             , ds_DigestMethod => { Algorithm => $digest }
108             };
109             }
110              
111             my $canonical = +{ Algorithm => $canon, $inclw->($doc, $preflist) };
112              
113             my $siginfo = $infow->($doc,
114             +{ ds_CanonicalizationMethod => $canonical
115             , ds_Reference => \@refs
116             , ds_SignatureMethod => { Algorithm => $sign_method }
117             } );
118             # warn "SIGINFO = $siginfo";
119              
120             my $si_canon = $canonic->($cleanup->($siginfo, @$preflist)); # to sign
121             ($siginfo, $si_canon);
122             };
123             }
124              
125              
126             # the digest algorithms can be distiguish by pure lowercase, no dash.
127             my $digest_algorithm =qr/^(?:
128             \Q${\DSIG_NS}\E
129             | \Q${\DSIG_MORE_NS}\E
130             | \Q${\XENC_NS}\E
131             ) ([a-z0-9]+)$
132             /x;
133              
134             sub _get_digester($$)
135             { my ($self, $method, $canonic) = @_;
136             $method =~ $digest_algorithm
137             or error __x"digest {name} is not supported", name => $method;
138             my $algo = uc $1;
139              
140             sub {
141             my $node = shift;
142             my $digest = try
143             { Digest::SHA->new($algo) # Digest objects cannot be reused
144             ->add($canonic->($node))
145             ->digest; # becomes base64 via XML field type
146             };
147             #use MIME::Base64;
148             #warn "DIGEST=", encode_base64 $digest;
149             $@ or return $digest;
150              
151             error __x"digest method {short} (for {name}): {err}"
152             , short => $algo, name => $method, err => $@->wasFatal;
153             };
154             }
155              
156             sub _digest_check($$)
157             { my ($self, $wss) = @_;
158              
159             # The horrible reality is that these settings may change per message,
160             # so we cannot keep the knowledge of the previous message. In practice,
161             # the settings will probably never ever change for an implementation.
162             sub {
163             my ($elem, $ref) = @_;
164             my $canon = $self->defaultCanonMethod;
165             my $preflist; # warning: prefixlist [] ne 'undef'!
166             my @removed;
167             foreach my $transf (@{$ref->{ds_Transforms}{ds_Transform}})
168             { my $algo = $transf->{Algorithm};
169             if(is_canon_constant $algo)
170             { $canon = $algo;
171             if(my $r = $transf->{cho_any})
172             { my ($inclns, $p) = %{$r->[0]}; # only 1 kv pair
173             $preflist = $p->{PrefixList};
174             }
175             }
176             elsif($algo eq DSIG_ENV_SIG)
177             { # enveloped-signature. $elem is am inside signed object
178             # it must be removed before signing. However, later we
179             # will use the content of the signature, so we have to
180             # glue it back.
181             push @removed, $elem->removeChild($_)
182             for $elem->getChildrenByLocalName('Signature');
183             }
184             else
185             { trace __x"unknown transform algorithm {name} ignored"
186             , name => $algo;
187             }
188             }
189             my $digmeth = $ref->{ds_DigestMethod}{Algorithm}
190             || $self->defaultDigestMethod;
191              
192             my $canonic = $self->_get_canonic($canon, $preflist);
193             my $digester = $self->_get_digester($digmeth, $canonic);
194             #use MIME::Base64;
195             #warn "IS? ".encode_base64($digester->($elem)), '==', encode_base64($ref->{ds_DigestValue});
196             my $correct = $digester->($elem) eq $ref->{ds_DigestValue};
197             #warn "CORRECT? $correct#";
198             $elem->addChild($_) for @removed;
199             $correct;
200             };
201             }
202              
203              
204             sub _get_canonic($$)
205             { my ($self, $canon, $preflist) = @_;
206             my $c14n = $self->c14n;
207              
208             sub
209             { my $node = shift or return '';
210             $c14n->normalize($canon, $node, prefix_list => $preflist);
211             };
212             }
213              
214             # only the inclusiveNamespaces of the Canon, while that's an 'any'
215             sub _canon_incl($)
216             { my ($self, $wss) = @_;
217             my $schema = $wss->schema;
218             my $type = $schema->findName('c14n:InclusiveNamespaces');
219             my $inclw = $schema->writer($type, include_namespaces => 0);
220             my $prefix = $schema->prefixed($type);
221              
222             sub {
223             my ($doc, $preflist) = @_;
224             defined $preflist or return;
225             ($type => $inclw->($doc, {PrefixList => $preflist}));
226             };
227             }
228              
229             # XML::Compile plays nasty tricks while constructing the XML tree,
230             # which break normalisation. The only way around that -on the moment-
231             # is to reparse the XML produced :(
232             # The next can be slow and is ugly, Sorry. MO
233              
234             sub _get_repair_xml($)
235             { my ($self, $wss) = @_;
236             my $preftab = $wss->schema->byPrefixTable;
237             my %preftab = map +($_ => $preftab->{$_}{uri}), keys %$preftab;
238              
239             sub {
240             my ($xc_out_dom, @preflist) = @_;
241              
242             # only doc element does charsets correctly
243             my $doc = XML::LibXML::Document->new('1.0', 'UTF8');
244              
245             # building bottom up: be sure we have all namespaces which may be
246             # declared later, on higher in the hierarchy.
247             my $env = $doc->createElement('Dummy');
248             $env->setNamespace($preftab{$_}, $_)
249             for keys %preftab;
250              
251             # reparse tree
252             $env->addChild($xc_out_dom->cloneNode(1));
253             my $fixed_dom = XML::LibXML->load_xml(string => $env->toString(0));
254             my $new_out = ($fixed_dom->documentElement->childNodes)[0];
255             $doc->importNode($new_out);
256             $new_out;
257             };
258             }
259              
260             sub checker($$$)
261             { my ($self, $wss, %args) = @_;
262             my $check = $self->_digest_check;
263              
264             sub {
265             my ($info, $elems, $tokens) = @_;
266              
267             my %references;
268             foreach my $ref (@{$info->{ds_Reference}})
269             { my $uri = $ref->{URI};
270             $uri =~ s/^#//;
271             $references{$uri} = $ref;
272             }
273              
274             foreach my $node (@$elems)
275             { # Sometimes "id" (Signature), sometimes "wsu:Id" (other)
276             my $id = $node->getAttribute('Id') # Signature/KeyInfo
277             || $node->getAttributeNS(WSU_NS, 'Id')
278             || $node->getAttribute('id'); # SMD::SignedMark
279              
280             $id or error __x"node to check signature without Id, {type}"
281             , type => type_of_node $node;
282              
283             my $ref = delete $references{$id}
284             or next; # maybe in other signature block
285              
286             $check->($node, $ref)
287             or error __x"digest info of {elem} is wrong", elem => $id;
288             }
289              
290             trace __x"reference {uri} not used", uri => $_
291             for keys %references;
292             };
293             }
294              
295             1;