File Coverage

lib/XML/Compile/WSS/Signature.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


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   564 use warnings;
  1         1  
  1         25  
6 1     1   3 use strict;
  1         1  
  1         21  
7              
8             package XML::Compile::WSS::Signature;
9 1     1   2 use vars '$VERSION';
  1         1  
  1         33  
10             $VERSION = '2.02';
11              
12 1     1   3 use base 'XML::Compile::WSS';
  1         1  
  1         50  
13              
14 1     1   3 use Log::Report 'xml-compile-wss-sig';
  1         1  
  1         3  
15              
16 1     1   160 use XML::Compile::WSS::Util qw/:wss11 :wsm10 :dsig :xtp10/;
  1         1  
  1         188  
17 1     1   4 use XML::Compile::WSS::SecToken ();
  1         10  
  1         12  
18 1     1   272 use XML::Compile::WSS::Sign ();
  1         1  
  1         14  
19 1     1   280 use XML::Compile::WSS::KeyInfo ();
  1         2  
  1         17  
20 1     1   280 use XML::Compile::WSS::SignedInfo ();
  0            
  0            
21              
22             use XML::Compile::C14N::Util qw/:c14n/;
23             use XML::Compile::C14N ();
24              
25             use Digest ();
26             use XML::LibXML ();
27             use File::Basename qw/dirname/;
28             use File::Glob qw/bsd_glob/;
29             use Scalar::Util qw/blessed/;
30              
31             my %prefixes =
32             ( # ds=DSIG_NS already registered in ::WSS
33             dsig11 => DSIG11_NS
34             , dsp => DSP_NS
35             , dsigm => DSIG_MORE_NS
36             , xenc => XENC_NS
37             );
38              
39             #use Data::Dumper;
40             #$Data::Dumper::Indent = 1;
41             #$Data::Dumper::Quotekeys = 0;
42              
43              
44             sub init($)
45             { my ($self, $args) = @_;
46             my $wss_v = $args->{wss_version} ||= '1.1';
47              
48             $self->SUPER::init($args);
49              
50             my $signer = delete $args->{signer} || {};
51             blessed $signer || ref $signer
52             or $signer = +{ sign_method => $signer }; # pre 2.00
53              
54             $signer->{private_key} ||= delete $args->{private_key}; # pre 2.00
55             $self->{XCWS_signer} =
56             XML::Compile::WSS::Sign->fromConfig(%$signer, wss => $self);
57              
58             my $si = delete $args->{signed_info} || {};
59             $si->{$_} ||= delete $args->{$_}
60             for qw/digest_method cannon_method prefix_list/; # pre 2.00
61              
62             $self->{XCWS_siginfo} =
63             XML::Compile::WSS::SignedInfo->fromConfig(%$si, wss => $self);
64              
65             my $ki = delete $args->{key_info} || {};
66             $ki->{publish_token} ||= delete $args->{publish_token}; # pre 2.00
67              
68             $self->{XCWS_keyinfo} =
69             XML::Compile::WSS::KeyInfo->fromConfig(%$ki, wss => $self);
70              
71             if(my $subsig = delete $args->{signature})
72             { $subsig->{sign_types} ||= [ 'wsse:SignatureType' ];
73             $subsig->{sign_put} ||= $args->{sign_put};
74             $self->{XCWS_subsig} = (ref $self)
75             ->new(wss_version => $wss_v, schema => $self->schema, %$subsig);
76             }
77              
78             $self->{XCWS_token} = $args->{token};
79             $self->{XCWS_config} = $args; # the left-overs are for me
80             $self;
81             }
82              
83             #-----------------------------
84              
85              
86             sub keyInfo() {shift->{XCWS_keyinfo}}
87             sub signedInfo() {shift->{XCWS_siginfo}}
88             sub signer() {shift->{XCWS_signer}}
89              
90             #-----------------------------
91              
92              
93             sub token() {shift->{XCWS_token}}
94             sub remoteToken() {shift->{XCWS_rem_token}}
95              
96             #-----------------------------
97             #### HELPERS
98              
99             sub prepareReading($)
100             { my ($self, $schema) = @_;
101             $self->SUPER::prepareReading($schema);
102              
103             my $config = $self->{XCWS_config};
104             if(my $r = $config->{remote_token})
105             { $self->{XCWS_rem_token} = XML::Compile::WSS::SecToken->fromConfig($r);
106             }
107              
108             my (@elems_to_check, $container, @signature_elems);
109             $schema->addHook
110             ( action => 'READER'
111             , type => ($config->{sign_types} or panic)
112             , before => sub {
113             my ($node, $path) = @_;
114             push @elems_to_check, $node;
115             $node;
116             }
117             );
118              
119             # we need the unparsed node to canonicalize and check
120             $schema->addHook
121             ( action => 'READER'
122             , type => 'ds:SignedInfoType'
123             , after => 'XML_NODE'
124             );
125              
126             # collect the elements to check, while decoding them
127             my $sign_put = $config->{sign_put} or panic;
128             $schema->addHook
129             ( action => 'READER'
130             , type => $sign_put
131             , after => sub {
132             my ($xml, $data, $path) = @_;
133             #warn "Located signature at $path";
134             push @signature_elems, $data->{ds_Signature}
135             if $data->{ds_Signature};
136             $container = $data;
137             $data;
138             }
139             );
140              
141             my $check_signature = $self->checker;
142             my $sign_when = $config->{sign_when} || $sign_put;
143             $schema->addHook
144             ( action => 'READER'
145             , type => $sign_when
146             , after => sub {
147             my ($xml, $data, $path) = @_;
148             #warn "Checking signatures when at $path";
149             @signature_elems
150             or error __x"signature element not found in answer";
151              
152             # We can leave the checking via exceptions, so have to reset
153             # the counters for the next message first.
154             my @e = @elems_to_check; @elems_to_check = ();
155             my @s = @signature_elems; @signature_elems = ();
156              
157             $check_signature->($container, $_, \@e) for @s;
158             $data;
159             }
160             );
161              
162             $self;
163             }
164              
165             # The checker routines throw an exception on error
166             sub checker($@)
167             { my $self = shift;
168             my $config = $self->{XCWS_config};
169             my %args = (%$config, @_);
170              
171             my $si = $self->signedInfo;
172             my $si_checker = $si->checker($self, %args);
173             my $get_tokens = $self->keyInfo->getTokens($self, %args);
174              
175             sub {
176             my ($container, $sig, $elems) = @_;
177             my $ki = $sig->{ds_KeyInfo};
178             my @tokens = $ki ? $get_tokens->($ki, $container, $sig->{Id}) : ();
179              
180             # Hey, you try to get tokens up in the hierachy in a recursive
181             # nested program yourself!
182             $ki->{__TOKENS} = \@tokens;
183              
184             ### check the signed-info content
185              
186             my $info = $sig->{ds_SignedInfo};
187             $si_checker->($info, $elems, \@tokens);
188              
189             ### Check the signature of the whole block
190              
191             my $canon = $info->{ds_CanonicalizationMethod};
192             my $preflist = $canon->{c14n_InclusiveNamespaces}{PrefixList}; # || [];
193             my $canonic = $si->_get_canonic($canon->{Algorithm}, $preflist);
194             my $sigvalue = $sig->{ds_SignatureValue}{_};
195              
196             my $signer = XML::Compile::WSS::Sign->new
197             ( sign_method => $info->{ds_SignatureMethod}{Algorithm}
198             , public_key => $tokens[0]
199             );
200              
201             $signer->checker->($canonic->($info->{_XML_NODE}), $sigvalue)
202             or error __x"received signature value is incorrect";
203              
204             };
205             }
206              
207             sub builder(%)
208             { my $self = shift;
209             my $config = $self->{XCWS_config};
210             my %args = (%$config, @_);
211            
212             my $signer = $self->signer;
213             my $signmeth = $signer->signMethod;
214             my $sign = $signer->builder($self, %args);
215             my $signedinfo = $self->signedInfo->builder($self, %args);
216             my $keylink = $self->keyInfo->builder($self, %args);
217             my $token = $self->token;
218             my $tokenw = $token->isa('XML::Compile::WSS::SecToken::EncrKey')
219             ? $token->builder($self, %args) : undef;
220              
221             my $sigw = $self->schema->writer('ds:Signature');
222              
223             # sign the signature!
224             my $subsign;
225             if(my $subsig = $self->{XCWS_subsig})
226             { $subsign = $subsig->builder;
227             }
228              
229             my $unique = time;
230              
231             sub {
232             my ($doc, $elems, $sec_node) = @_;
233             my ($sinfo, $si_canond) = $signedinfo->($doc, $elems, $signmeth);
234              
235             $sec_node->appendChild($tokenw->($doc, $sec_node))
236             if $tokenw;
237              
238             my $signature = $sign->($si_canond);
239             my %sig =
240             ( ds_SignedInfo => $sinfo
241             , ds_SignatureValue => {_ => $signature}
242             , ds_KeyInfo => $keylink->($doc, $token, $sec_node)
243             , Id => 'SIG-'.$unique++
244             );
245             my $signode = $sigw->($doc, \%sig);
246             $sec_node->appendChild($signode);
247              
248             $subsign->($doc, [$signode], $sec_node)
249             if $subsign;
250              
251             $sec_node;
252             };
253             }
254              
255             sub prepareWriting($)
256             { my ($self, $schema) = @_;
257             $self->SUPER::prepareWriting($schema);
258              
259             $self->token
260             or error __x"creating signatures needs a token";
261              
262             my $config = $self->{XCWS_config};
263              
264             my @elems_to_sign;
265             my $sign_types = $config->{sign_types} or panic;
266             my @sign_types = ref $sign_types eq 'ARRAY' ? @$sign_types : $sign_types;
267              
268             $schema->addHook
269             ( action => 'WRITER'
270             , type => \@sign_types
271             , after => sub {
272             my ($doc, $xml, $path, $val, $type) = @_;
273              
274             # Not all schemas demand an explicit Id on the signed element, so
275             # we may need to force one.
276             my $has = $xml->getAttributeNS(WSU_10, 'Id')
277             || $xml->getAttribute('wsu:Id');
278              
279             my $wsuid = $val->{wsu_Id};
280             if($has)
281             { error __x"element {type} wants two wsu:Id's: {one} and {two}"
282             , type => $type, one => $wsuid, two => $has
283             if $has ne $wsuid;
284             }
285             else
286             { $xml->setNamespace(WSU_10, wsu => 0);
287             $xml->setAttributeNS(WSU_10, Id => $wsuid || 'node-'.($xml+0));
288             # Above two lines do add a xmlns:wsu per Id.
289             }
290              
291             push @elems_to_sign, $xml;
292             $xml;
293             }
294             );
295              
296             my $sign_put = $config->{sign_put} or panic;
297             my $sign_when = $config->{sign_when} || $sign_put;
298              
299             my $enveloped = grep $sign_put eq $_, @sign_types;
300             if($enveloped)
301             { # The Signature element is required in the enveloped element,
302             # but can only be created after the element has been produced. This
303             # is a chicken-egg situation. Gladly, XML::Compile does not check
304             # the kind of node which get's produced, so we can put in an empty
305             # text-node which gets ignored.
306             $schema->addHook
307             ( action => 'WRITER', type => 'ds:SignatureType'
308             , replace => sub { $_[0]->createTextNode('') }
309             );
310             }
311              
312             my $add_signature = $self->builder(enveloped => $enveloped);
313              
314             my $container;
315             $schema->addHook
316             ( action => 'WRITER'
317             , type => $sign_put
318             , after => sub {
319             my ($doc, $xml) = @_;
320             #warn "Located signature container";
321             # $schema->prefixFor(WSU_10);
322             $container = $xml;
323             }
324             );
325              
326             $schema->addHook
327             ( action => 'WRITER'
328             , type => $sign_when
329             , after => sub {
330             my ($doc, $xml) = @_;
331             #warn "Creating signature";
332             $add_signature->($doc, \@elems_to_sign, $container);
333             @elems_to_sign = ();
334             $xml;
335             }
336             );
337              
338             $self;
339             }
340              
341             sub loadSchemas($$)
342             { my ($self, $schema, $version) = @_;
343             return if $schema->{XCWS_sig_loaded}++;
344              
345             $self->SUPER::loadSchemas($schema, $version);
346              
347             my $xsddir = dirname __FILE__;
348             trace "loading wss-dsig schemas from $xsddir/(dsig|encr)/*.xsd";
349              
350             my @xsds =
351             ( bsd_glob("$xsddir/dsig/*.xsd")
352             , bsd_glob("$xsddir/encr/*.xsd")
353             );
354              
355             $schema->addPrefixes(\%prefixes);
356             my $prefixes = join ',', sort keys %prefixes;
357             $schema->addKeyRewrite("PREFIXED($prefixes)");
358              
359             $schema->importDefinitions(\@xsds);
360             $schema;
361             }
362              
363             1;