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; |