File Coverage

lib/XML/Compile/WSS/Signature.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-2013 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.01.
5 1     1   2439 use warnings;
  1         2  
  1         397  
6 1     1   9 use strict;
  1         3  
  1         59  
7              
8             package XML::Compile::WSS::Signature;
9 1     1   6 use vars '$VERSION';
  1         2  
  1         62  
10             $VERSION = '2.01';
11              
12 1     1   7 use base 'XML::Compile::WSS';
  1         2  
  1         600  
13              
14 1     1   7 use Log::Report 'xml-compile-wss-sig';
  1         3  
  1         11  
15              
16 1     1   876 use XML::Compile::WSS::Util qw/:wss11 :wsm10 :dsig :xtp10/;
  0            
  0            
17             use XML::Compile::WSS::SecToken ();
18             use XML::Compile::WSS::Sign ();
19             use XML::Compile::WSS::KeyInfo ();
20             use XML::Compile::WSS::SignedInfo ();
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 defined 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             $signer->{$_} ||= delete $args->{$_} # pre 2.00
54             for qw/private_key/;
55             $self->{XCWS_signer} = XML::Compile::WSS::Sign
56             ->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} = XML::Compile::WSS::SignedInfo
63             ->fromConfig(%$si, wss => $self);
64              
65             my $ki = delete $args->{key_info} || {};
66             $ki->{$_} ||= delete $args->{$_}
67             for qw/publish_token/; # pre 2.00
68              
69             $self->{XCWS_keyinfo} = XML::Compile::WSS::KeyInfo
70             ->fromConfig(%$ki, wss => $self);
71              
72             if(my $subsig = delete $args->{signature})
73             { $self->{XCWS_subsig} = (ref $self)->new(wss_version => $wss_v
74             , schema => $self->schema, %$subsig);
75             }
76              
77             $self->{XCWS_token} = $args->{token};
78              
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             $schema->addHook
128             ( action => 'READER'
129             , type => ($config->{sign_put} || panic)
130             , after => sub {
131             my ($xml, $data, $path) = @_;
132             #warn "Located signature at $path";
133             push @signature_elems, $data->{ds_Signature}
134             if $data->{ds_Signature};
135             $container = $data;
136             $data;
137             }
138             );
139              
140             my $check_signature = $self->checker;
141             $schema->addHook
142             ( action => 'READER'
143             , type => ($config->{sign_when} || panic)
144             , after => sub {
145             my ($xml, $data, $path) = @_;
146             #warn "Checking signatures when at $path";
147             @signature_elems
148             or error __x"signature element not found in answer";
149              
150             # We can leave the checking via exceptions, so have to reset
151             # the counters for the next message first.
152             my @e = @elems_to_check; @elems_to_check = ();
153             my @s = @signature_elems; @signature_elems = ();
154              
155             $check_signature->($container, $_, \@e) for @s;
156             $data;
157             }
158             );
159              
160             $self;
161             }
162              
163             # The checker routines throw an exception on error
164             sub checker($@)
165             { my $self = shift;
166             my $config = $self->{XCWS_config};
167             my %args = (%$config, @_);
168              
169             my $si = $self->signedInfo;
170             my $si_checker = $si->checker($self, %args);
171             my $get_tokens = $self->keyInfo->getTokens($self, %args);
172              
173             sub {
174             my ($container, $sig, $elems) = @_;
175             my $ki = $sig->{ds_KeyInfo};
176             my @tokens = $ki ? $get_tokens->($ki, $container, $sig->{Id}) : ();
177              
178             # Hey, you try to get tokens up in the hierachy in a recursive
179             # nested program yourself!
180             $ki->{__TOKENS} = \@tokens;
181              
182             ### check the signed-info content
183              
184             my $info = $sig->{ds_SignedInfo};
185             $si_checker->($info, $elems, \@tokens);
186              
187             ### Check the signature of the whole block
188              
189             my $canon = $info->{ds_CanonicalizationMethod};
190             my $preflist = $canon->{c14n_InclusiveNamespaces}{PrefixList}; # || [];
191             my $canonic = $si->_get_canonic($canon->{Algorithm}, $preflist);
192             my $sigvalue = $sig->{ds_SignatureValue}{_};
193              
194             my $signer = XML::Compile::WSS::Sign->new
195             ( sign_method => $info->{ds_SignatureMethod}{Algorithm}
196             , public_key => $tokens[0]
197             );
198              
199             $signer->checker->($canonic->($info->{_XML_NODE}), $sigvalue)
200             or error __x"received signature value is incorrect";
201              
202             };
203             }
204              
205             sub builder(%)
206             { my $self = shift;
207             my $config = $self->{XCWS_config};
208             my %args = (%$config, @_);
209            
210             my $signer = $self->signer;
211             my $signmeth = $signer->signMethod;
212             my $sign = $signer->builder($self, %args);
213             my $signedinfo = $self->signedInfo->builder($self, %args);
214             my $keylink = $self->keyInfo->builder($self, %args);
215             my $token = $self->token;
216             my $tokenw = $token->isa('XML::Compile::WSS::SecToken::EncrKey')
217             ? $token->builder($self, %args) : undef;
218              
219             my $sigw = $self->schema->writer('ds:Signature');
220              
221             # sign the signature!
222             my $subsign;
223             if(my $subsig = $self->{XCWS_subsig})
224             { $subsign = $subsig->builder;
225             }
226              
227             my $unique = time;
228              
229             sub {
230             my ($doc, $elems, $sec_node) = @_;
231             my ($sinfo, $si_canond) = $signedinfo->($doc, $elems, $signmeth);
232              
233             $sec_node->appendChild($tokenw->($doc, $sec_node))
234             if $tokenw;
235              
236             my $signature = $sign->($si_canond);
237             my %sig =
238             ( ds_SignedInfo => $sinfo
239             , ds_SignatureValue => {_ => $signature}
240             , ds_KeyInfo => $keylink->($doc, $token, $sec_node)
241             , Id => 'SIG-'.$unique++
242             );
243             my $signode = $sigw->($doc, \%sig);
244             $sec_node->appendChild($signode);
245              
246             $subsign->($doc, [$signode], $sec_node)
247             if $subsign;
248              
249             $sec_node;
250             };
251             }
252              
253             sub prepareWriting($)
254             { my ($self, $schema) = @_;
255             $self->SUPER::prepareWriting($schema);
256              
257             $self->token
258             or error __x"creating signatures needs a token";
259              
260             my $config = $self->{XCWS_config};
261              
262             my @elems_to_sign;
263             $schema->addHook
264             ( action => 'WRITER'
265             , type => ($config->{sign_types} or panic)
266             , after => sub {
267             my ($doc, $xml) = @_;
268              
269             unless($xml->getAttributeNS(WSU_10, 'Id'))
270             { my $wsuid = 'node-'.($xml+0); # configurable?
271             $xml->setNamespace(WSU_10, wsu => 0);
272             $xml->setAttributeNS(WSU_10, Id => $wsuid);
273              
274             # Above two lines do add a xml:wsu per Id. Below does not,
275             # which is not always enough: elements live in weird places
276             # my $wsu = $schema->prefixFor(WSU_10);
277             # $xml->setAttribute("$wsu:Id", $wsuid);
278             }
279              
280             #use XML::Compile::Util qw/type_of_node/;
281             #warn "Registering to sign ".type_of_node($xml);
282             push @elems_to_sign, $xml;
283             $xml;
284             }
285             );
286              
287             my $container;
288             $schema->addHook
289             ( action => 'WRITER'
290             , type => ($config->{sign_put} || panic)
291             , after => sub {
292             my ($doc, $xml) = @_;
293             #warn "Located signature container";
294             # $schema->prefixFor(WSU_10);
295             $container = $xml;
296             }
297             );
298              
299             my $add_signature = $self->builder;
300             $schema->addHook
301             ( action => 'WRITER'
302             , type => ($config->{sign_when} || panic)
303             , after => sub {
304             my ($doc, $xml) = @_;
305             #warn "Creating signature";
306             $add_signature->($doc, \@elems_to_sign, $container);
307             @elems_to_sign = ();
308             $xml;
309             }
310             );
311              
312             $self;
313             }
314              
315             sub loadSchemas($$)
316             { my ($self, $schema, $version) = @_;
317             return if $schema->{XCWS_sig_loaded}++;
318              
319             $self->SUPER::loadSchemas($schema, $version);
320              
321             my $xsddir = dirname __FILE__;
322             trace "loading wss-dsig schemas from $xsddir/(dsig|encr)/*.xsd";
323              
324             my @xsds =
325             ( bsd_glob("$xsddir/dsig/*.xsd")
326             , bsd_glob("$xsddir/encr/*.xsd")
327             );
328              
329             $schema->addPrefixes(\%prefixes);
330             my $prefixes = join ',', sort keys %prefixes;
331             $schema->addKeyRewrite("PREFIXED($prefixes)");
332              
333             $schema->importDefinitions(\@xsds);
334              
335             $schema;
336             }
337              
338             1;