File Coverage

blib/lib/XML/Enc.pm
Criterion Covered Total %
statement 428 473 90.4
branch 88 136 64.7
condition 28 50 56.0
subroutine 55 57 96.4
pod 3 3 100.0
total 602 719 83.7


line stmt bran cond sub pod time code
1 10     10   2072332 use strict;
  10         22  
  10         419  
2 10     10   62 use warnings;
  10         20  
  10         1079  
3              
4             package XML::Enc;
5             our $VERSION = '0.15'; # VERSION
6              
7             # ABSTRACT: XML::Enc Encryption Support
8              
9 10     10   74 use Carp;
  10         23  
  10         1036  
10 10     10   6402 use Crypt::AuthEnc::GCM 0.062;
  10         43619  
  10         694  
11 10     10   5342 use Crypt::Mode::CBC;
  10         11456  
  10         441  
12 10     10   6053 use Crypt::PK::RSA 0.081;
  10         157550  
  10         741  
13 10     10   97 use Crypt::PRNG qw( random_bytes );
  10         20  
  10         2822  
14 10     10   10318 use MIME::Base64 qw/decode_base64 encode_base64/;
  10         10097  
  10         842  
15 10     10   7362 use XML::LibXML;
  10         727444  
  10         84  
16              
17             # state means perl 5.10
18 10     10   3062 use feature 'state';
  10         25  
  10         1855  
19 10     10   75 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS $DEBUG);
  10         24  
  10         88501  
20              
21             our $DEBUG = 0;
22              
23              
24             # Source: https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#sec-Alg-Block
25             # 5.2.1 Triple DES - 64 bit Initialization Vector (IV) (8 bytes)
26             # 5.2.2 AES - 128 bit initialization vector (IV) (16 bytes)
27              
28             sub _assert_symmetric_algorithm {
29 464     464   1087 my $algo = shift;
30              
31 464         3021 state $SYMMETRIC = {
32             'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => {
33             ivsize => 8,
34             keysize => 24,
35             modename => 'DES_EDE'
36             },
37             'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => {
38             ivsize => '16',
39             keysize => 16,
40             modename => 'AES'
41             },
42             'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => {
43             ivsize => '16',
44             keysize => 24,
45             modename => 'AES'
46             },
47             'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => {
48             ivsize => '16',
49             keysize => 32,
50             modename => 'AES'
51             },
52             'http://www.w3.org/2009/xmlenc11#aes128-gcm' => {
53             ivsize => '12',
54             keysize => 16,
55             modename => 'AES',
56             tagsize => 16
57             },
58             'http://www.w3.org/2009/xmlenc11#aes192-gcm' => {
59             ivsize => '12',
60             keysize => 24,
61             modename => 'AES',
62             tagsize => 16
63             },
64             'http://www.w3.org/2009/xmlenc11#aes256-gcm' => {
65             ivsize => '12',
66             keysize => 32,
67             modename => 'AES',
68             tagsize => 16
69             },
70             };
71              
72 464 50       2235 die "Unsupported symmetric algo $algo" unless $SYMMETRIC->{ $algo };
73 464         1134 return $SYMMETRIC->{$algo}
74             }
75              
76             sub _assert_encryption_digest {
77 212     212   581 my $algo = shift;
78              
79 212         488 state $ENC_DIGEST = {
80             'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
81             'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
82             'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
83             'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
84             'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
85             };
86 212 50       1072 die "Unsupported encryption digest algo $algo" unless $ENC_DIGEST->{ $algo };
87 212         755 return $ENC_DIGEST->{ $algo };
88             }
89              
90              
91              
92             sub new {
93 240     240 1 2406194 my $class = shift;
94 240         590 my $params = shift;
95 240         699 my $self = {};
96              
97 240         626 bless $self, $class;
98              
99 240 50       1223 if ( exists $params->{ 'key' } ) {
100 240         2199 $self->{key} = $params->{ 'key' };
101 240         1476 $self->_load_key( $params->{ 'key' } );
102             }
103 240 100       1640 if ( exists $params->{ 'cert' } ) {
104 227         736 $self->{cert} = $params->{ 'cert' };
105 227         1359 $self->_load_cert_file( $params->{ 'cert' } );
106             }
107 240 50       1185 if (exists $params->{'no_xml_declaration'}) {
108 240 50       1186 $self->{'no_xml_declaration'} = $params->{'no_xml_declaration'} ? $params->{'no_xml_declaration'} : 0;
109             }
110              
111 240 100       1098 my $enc_method = exists($params->{'data_enc_method'}) ? $params->{'data_enc_method'} : 'aes256-cbc';
112 240         1311 $self->{'data_enc_method'} = $self->_setEncryptionMethod($enc_method);
113              
114 240 100       1122 my $key_method = exists($params->{'key_transport'}) ? $params->{'key_transport'} : 'rsa-oaep-mgf1p ';
115 240         1080 $self->{'key_transport'} = $self->_setKeyEncryptionMethod($key_method);
116              
117 240 100       1006 if (exists $params->{'oaep_mgf_alg'}) {
118 210         848 $self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($params->{'oaep_mgf_alg'});
119             }
120 240 100       1858 if (exists $params->{'oaep_label_hash'} ) {
121 210         868 $self->{'oaep_label_hash'} = $self->_setOAEPDigest($params->{'oaep_label_hash'});
122             }
123              
124 240 100       1380 $self->{'oaep_params'} = exists($params->{'oaep_params'}) ? $params->{'oaep_params'} : '';
125              
126 240 100       892 $self->{'key_name'} = $params->{'key_name'} if exists($params->{'key_name'});
127              
128 240         1958 return $self;
129             }
130              
131              
132             sub decrypt {
133 240     240 1 521558 my $self = shift;
134 240         755 my $xml = shift;
135 240         689 my %options = @_;
136              
137 240         681 local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };
138              
139 240         1393 my $doc = XML::LibXML->load_xml( string => $xml );
140              
141 239         114001 my $xpc = XML::LibXML::XPathContext->new($doc);
142 239         2069 $xpc->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
143 239         1004 $xpc->registerNs('xenc', 'http://www.w3.org/2001/04/xmlenc#');
144 239         961 $xpc->registerNs('xenc11', 'http://www.w3.org/2009/xmlenc11#');
145 239         843 $xpc->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
146              
147 239 50       1247 return $doc unless $xpc->exists('//xenc:EncryptedData');
148              
149 239 50       12644 die "You cannot decrypt XML without a private key." unless $self->{key_obj};
150              
151 239         1013 my $parser = XML::LibXML->new();
152 239         8538 $self->_decrypt_encrypted_key_nodes($xpc, $parser, %options);
153 239         14686 $self->_decrypt_uri_nodes($xpc, $parser, %options);
154              
155 239         1175 return $doc->serialize();
156             }
157              
158             sub _decrypt_encrypted_key_nodes {
159 239     239   497 my $self = shift;
160 239         507 my $xpc = shift;
161 239         399 my $parser = shift;
162 239         621 my %options = @_;
163              
164             my $k = $self->_get_named_key_nodes(
165             '//xenc:EncryptedData/dsig:KeyInfo/xenc:EncryptedKey',
166             $xpc, $options{key_name}
167 239         1407 );
168              
169             $k->foreach(
170             sub {
171 238     238   3651 my $key = $self->_get_key_from_node($_, $xpc);
172 238 100       1096 return unless $key;
173 237         4892 my $encrypted_node = $_->parentNode->parentNode;
174 237         2625 $self->_decrypt_encrypted_node($encrypted_node,
175             $key, $xpc, $parser);
176             }
177 239         2800 );
178             }
179              
180             sub _decrypt_uri_nodes {
181 239     239   3960 my $self = shift;
182 239         461 my $xpc = shift;
183 239         575 my $parser = shift;
184 239         679 my %options = @_;
185              
186 239         919 my $uri_nodes = $xpc->findnodes('//dsig:KeyInfo/dsig:RetrievalMethod/@URI');
187 239     1   18493 my @uri_nodes = $uri_nodes->map(sub { my $v = $_->getValue; $v =~ s/^#//; return $v; });
  1         23  
  1         10  
  1         6  
188              
189 239         7383 foreach my $uri (@uri_nodes) {
190             my $encrypted_key_nodes = $self->_get_named_key_nodes(
191             sprintf('//xenc:EncryptedKey[@Id="%s"]', $uri),
192 1         7 $xpc, $options{key_name});
193              
194             $encrypted_key_nodes->foreach(
195             sub {
196              
197 1     1   14 my $key = $self->_get_key_from_node($_, $xpc);
198 1 50       29 return unless $key;
199              
200 1         16 my $encrypted_nodes = $xpc->findnodes(sprintf('//dsig:KeyInfo/dsig:RetrievalMethod[@URI="#%s"]/../..', $uri));
201 1 50       137 return unless $encrypted_nodes->size;
202              
203             $encrypted_nodes->foreach(sub {
204 1         19 $self->_decrypt_encrypted_node(
205             $_,
206             $key,
207             $xpc,
208             $parser
209             );
210 1         20 });
211              
212             # We don't need the encrypted key here
213 1         39 $_->removeChildNodes();
214             }
215 1         9 );
216             }
217             }
218              
219             sub _get_named_key_nodes {
220 240     240   482 my $self = shift;
221 240         500 my $xpath = shift;
222 240         396 my $xpc = shift;
223 240         632 my $name = shift;
224              
225 240         826 my $nodes = $xpc->findnodes($xpath);
226 240 50       13491 return $nodes unless $name;
227             return $nodes->grep(
228             sub {
229 0     0   0 $xpc->findvalue('dsig:KeyInfo/dsig:KeyName', $_) eq $name;
230             }
231 0         0 );
232             }
233              
234             sub _decrypt_encrypted_node {
235 238     238   9298 my $self = shift;
236 238         493 my $node = shift;
237 238         501 my $key = shift;
238 238         506 my $xpc = shift;
239 238         636 my $parser = shift;
240              
241 238         1322 my $algo = $self->_get_encryption_algorithm($node, $xpc);
242 238         7126 my $cipher_value = $self->_get_cipher_value($node, $xpc);
243 238         13674 my $oaep = $self->_get_oaep_params($node, $xpc);
244              
245 238         1281 my $decrypted_data = $self->_DecryptData($algo, $key, $cipher_value);
246              
247             # Sooo.. parse_balanced_chunk breaks when there is a 248             # bit in the decrypted data and thus we have to remove it.
249             # We try parsing the XML here and if that works we get all the nodes
250 238         540 my $new = eval { $parser->load_xml(string => $decrypted_data)->findnodes('//*')->[0]; };
  238         1982  
251              
252 238 100       126102 if ($new) {
253 236         4089 $node->addSibling($new);
254 236         540 $node->unbindNode();
255 236         4250 return;
256             }
257              
258 2         31 $decrypted_data = $parser->parse_balanced_chunk($decrypted_data);
259 2 50 50     620 if (($node->parentNode->localname //'') eq 'EncryptedID') {
260 0         0 $node->parentNode->replaceNode($decrypted_data);
261 0         0 return;
262             }
263 2         12 $node->replaceNode($decrypted_data);
264 2         85 return;
265             }
266              
267             sub _get_key_from_node {
268 239     239   419 my $self = shift;
269 239         423 my $node = shift;
270 239         449 my $xpc = shift;
271              
272 239         837 my $algo = $self->_get_encryption_algorithm($_, $xpc);
273 239         8084 my $cipher_value = $self->_get_cipher_value($_, $xpc);
274 239         16401 my $digest_name = $self->_get_digest_method($_, $xpc);
275 239         881 my $oaep = $self->_get_oaep_params($_, $xpc);
276 239         4594 my $mgf = $self->_get_mgf($_, $xpc);
277              
278 239         1354 return $self->_decrypt_key(
279             $cipher_value,
280             $algo,
281             $digest_name,
282             $oaep,
283             $mgf,
284             );
285             }
286              
287             sub _get_encryption_algorithm {
288 477     477   1227 my $self = shift;
289 477         863 my $node = shift;
290 477         891 my $xpc = shift;
291              
292 477         2670 my $nodes = $xpc->findnodes('./xenc:EncryptionMethod/@Algorithm', $node);
293 477 50       40239 return $nodes->get_node(1)->getValue if $nodes->size;
294 0         0 confess "Unable to determine encryption method algorithm from " . $node->nodePath;
295             }
296              
297             sub _get_cipher_value {
298 477     477   945 my $self = shift;
299 477         925 my $node = shift;
300 477         765 my $xpc = shift;
301              
302 477         1487 my $nodes = $xpc->findnodes('./xenc:CipherData/xenc:CipherValue', $node);
303 477 50       24440 return decode_base64($nodes->get_node(1)->textContent) if $nodes->size;
304 0         0 confess "Unable to get the CipherValue from " . $node->nodePath;
305             }
306              
307             sub _get_mgf {
308 239     239   445 my $self = shift;
309 239         438 my $node = shift;
310 239         372 my $xpc = shift;
311              
312 239         724 my $value = $xpc->findvalue('./xenc:EncryptionMethod/xenc11:MGF/@Algorithm', $node);
313 239 100       17272 return $value if $value;
314 64         151 return;
315             }
316              
317             sub _get_oaep_params {
318 477     477   1008 my $self = shift;
319 477         885 my $node = shift;
320 477         797 my $xpc = shift;
321              
322 477         1781 my $value = $xpc->findvalue('./xenc:EncryptionMethod/xenc:OAEPparams', $node);
323 477 100       43596 return decode_base64($value) if $value;
324 264         749 return;
325             }
326              
327             sub _get_digest_method {
328 239     239   508 my $self = shift;
329 239         426 my $node = shift;
330 239         433 my $xpc = shift;
331              
332 239         795 my $value = $xpc->findvalue(
333             './xenc:EncryptionMethod/dsig:DigestMethod/@Algorithm', $node);
334 239 100       21973 return _assert_encryption_digest($value) if $value;
335 27         126 return;
336             }
337              
338              
339             sub encrypt {
340 226     226 1 2134 my $self = shift;
341 226         657 my ($xml) = @_;
342              
343 226         625 local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };
344              
345             # Create the EncryptedData node
346 226         1007 my ($encrypted) = $self->_create_encrypted_data_xml();
347              
348 226         19822 my $dom = XML::LibXML->load_xml( string => $xml);
349              
350 226         82039 my $xpc = XML::LibXML::XPathContext->new($encrypted);
351 226         1904 $xpc->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
352 226         926 $xpc->registerNs('xenc', 'http://www.w3.org/2001/04/xmlenc#');
353 226         1111 $xpc->registerNs('xenc11', 'http://www.w3.org/2009/xmlenc11#');
354 226         947 $xpc->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
355              
356             # Encrypt the data an empty key is passed by reference to allow
357             # the key to be generated at the same time the data is being encrypted
358 226         385 my $key;
359 226         625 my $method = $self->{data_enc_method};
360 226         921 my $encrypteddata = $self->_EncryptData ($method, $dom->serialize(), \$key);
361              
362             # Encrypt the Key immediately after the data is encrypted. It is passed by
363             # reference to reduce the number of times that the unencrypted key is
364             # stored in memory
365 226         7559 $self->_EncryptKey($self->{key_transport}, \$key);
366              
367 226         1793 my $base64_key = encode_base64($key);
368 226         785 my $base64_data = encode_base64($encrypteddata);
369              
370             # Insert KeyName into the XML
371 226 100 66     1677 if (defined $self->{key_name} and $self->{key_name} ne '') {
372 224         1135 $encrypted = $self->_setKeyName($encrypted, $xpc, $self->{key_name});
373             }
374              
375             # Insert OAEPparams into the XML
376 226 100       4401 if ($self->{oaep_params} ne '') {
377 211         1348 $encrypted = $self->_setOAEPparams($encrypted, $xpc, encode_base64($self->{oaep_params}));
378             }
379              
380             # Insert Encrypted data into XML
381 226         3482 $encrypted = $self->_setEncryptedData($encrypted, $xpc, $base64_data);
382              
383             # Insert the Encrypted Key into the XML
384 226         3560 $self->_setKeyEncryptedData($encrypted, $xpc, $base64_key);
385              
386 226         3547 return $encrypted->serialize();
387             }
388              
389             sub _setEncryptionMethod {
390 240     240   543 my $self = shift;
391 240         475 my $method = shift;
392              
393 240         2841 my %methods = (
394             'aes128-cbc' => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
395             'aes192-cbc' => 'http://www.w3.org/2001/04/xmlenc#aes192-cbc',
396             'aes256-cbc' => 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
397             'tripledes-cbc' => 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
398             'aes128-gcm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm',
399             'aes192-gcm' => 'http://www.w3.org/2009/xmlenc11#aes192-gcm',
400             'aes256-gcm' => 'http://www.w3.org/2009/xmlenc11#aes256-gcm',
401             );
402              
403 240 50       2031 return exists($methods{$method}) ? $methods{$method} : $methods{'aes256-cbc'};
404             }
405              
406             sub _setKeyName {
407 224     224   544 my $self = shift;
408 224         473 my $context = shift;
409 224         356 my $xpc = shift;
410 224         457 my $keyname = shift;
411              
412 224         1295 my $node = $xpc->findnodes('//xenc:EncryptedKey/dsig:KeyInfo/dsig:KeyName', $context);
413              
414 224         21239 $node->[0]->removeChildNodes();
415 224 50       1972 $node->[0]->appendText(defined $keyname ? $keyname : 'key_name');
416 224         1235 return $context;
417             }
418              
419             sub _setOAEPparams {
420 211     211   427 my $self = shift;
421 211         442 my $context = shift;
422 211         372 my $xpc = shift;
423 211         437 my $oaep_params = shift;
424              
425 211         679 my $node = $xpc->findnodes('//xenc:EncryptedKey/xenc:EncryptionMethod/xenc:OAEPparams', $context);
426              
427 211         12475 $node->[0]->removeChildNodes();
428 211         979 $node->[0]->appendText($oaep_params);
429 211         756 return $context;
430             }
431              
432             sub _setOAEPAlgorithm {
433 210     210   481 my $self = shift;
434 210         717 my $method = shift;
435              
436 210         375 state $setOAEPAlgorithm = {
437             'mgf1sha1' => 'http://www.w3.org/2009/xmlenc11#mgf1sha1',
438             'mgf1sha224' => 'http://www.w3.org/2009/xmlenc11#mgf1sha224',
439             'mgf1sha256' => 'http://www.w3.org/2009/xmlenc11#mgf1sha256',
440             'mgf1sha384' => 'http://www.w3.org/2009/xmlenc11#mgf1sha384',
441             'mgf1sha512' => 'http://www.w3.org/2009/xmlenc11#mgf1sha512',
442             };
443              
444 210   66     1389 return $setOAEPAlgorithm->{$method} // $setOAEPAlgorithm->{'rsa-oaep-mgf1p'};
445             }
446              
447             sub _getOAEPAlgorithm {
448 350     350   733 my $self = shift;
449 350         685 my $method = shift;
450              
451 350         618 state $OAEPAlgorithm = {
452             'http://www.w3.org/2009/xmlenc11#mgf1sha1' => 'SHA1',
453             'http://www.w3.org/2009/xmlenc11#mgf1sha224' => 'SHA224',
454             'http://www.w3.org/2009/xmlenc11#mgf1sha256' => 'SHA256',
455             'http://www.w3.org/2009/xmlenc11#mgf1sha384' => 'SHA384',
456             'http://www.w3.org/2009/xmlenc11#mgf1sha512' => 'SHA512',
457             };
458              
459 350   50     9387919 return $OAEPAlgorithm->{$method} // 'SHA1';
460             }
461              
462             sub _setOAEPDigest {
463 210     210   393 my $self = shift;
464 210         508 my $method = shift;
465              
466 210         404 state $OAEPDigest = {
467             'sha1' => 'http://www.w3.org/2000/09/xmldsig#sha1',
468             'sha224' => 'http://www.w3.org/2001/04/xmldsig-more#sha224',
469             'sha256' => 'http://www.w3.org/2001/04/xmlenc#sha256',
470             'sha384' => 'http://www.w3.org/2001/04/xmldsig-more#sha384',
471             'sha512' => 'http://www.w3.org/2001/04/xmlenc#sha512',
472             };
473              
474 210   33     1053 return $OAEPDigest->{$method} // $OAEPDigest->{'sha256'};
475             }
476              
477             sub _getParamsAlgorithm {
478 210     210   444 my $self = shift;
479 210         459 my $method = shift;
480              
481 210         397 state $ParamsAlgorithm = {
482             'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
483             'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
484             'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
485             'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
486             'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
487             };
488              
489 210   33     973 return $ParamsAlgorithm->{$method} // $ParamsAlgorithm->{'http://www.w3.org/2000/09/xmldsig#sha1'};
490             }
491              
492             sub _setKeyEncryptionMethod {
493 240     240   660 my $self = shift;
494 240         494 my $method = shift;
495              
496 240         546 state $enc_methods = {
497             'rsa-1_5' => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
498             'rsa-oaep-mgf1p' => 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
499             'rsa-oaep' => 'http://www.w3.org/2009/xmlenc11#rsa-oaep',
500             };
501              
502 240   66     1530 return $enc_methods->{$method} // $enc_methods->{'rsa-oaep-mgf1p'};
503             }
504              
505             sub _DecryptData {
506 238     238   554 my $self = shift;
507 238         505 my $method = shift;
508 238         410 my $key = shift;
509 238         544 my $encrypteddata = shift;
510              
511 238         1059 my $method_vars = _assert_symmetric_algorithm($method);
512              
513 238         814 my $ivsize = $method_vars->{ivsize};
514 238         553 my $tagsize = $method_vars->{tagsize};
515              
516 238         966 my $iv = substr $encrypteddata, 0, $ivsize;
517 238         765 my $encrypted = substr $encrypteddata, $ivsize;
518              
519             # XML Encryption 5.2 Block Encryption Algorithms
520             # The resulting cipher text is prefixed by the IV.
521 238 100       1546 if ($method !~ /gcm/ ){
522 142         2127 my $cbc = Crypt::Mode::CBC->new($method_vars->{modename}, 0);
523 142         1179 return $self->_remove_padding($cbc->decrypt($encrypted, $key, $iv));
524             }
525              
526 96         31831 my $gcm = Crypt::AuthEnc::GCM->new("AES", $key, $iv);
527              
528             # Note that GCM support for additional authentication
529             # data is not used in the XML specification.
530 96         530 my $tag = substr $encrypted, -$tagsize;
531 96         344 $encrypted = substr $encrypted, 0, (length $encrypted) - $tagsize;
532 96         929 my $plaintext = $gcm->decrypt_add($encrypted);
533              
534 96 50       816 die "Tag expected did not match returned Tag"
535             unless $gcm->decrypt_done($tag);
536              
537 96         765 return $plaintext;
538             }
539              
540             sub _EncryptData {
541 226     226   14756 my $self = shift;
542 226         439 my $method = shift;
543 226         610 my $data = shift;
544 226         434 my $key = shift;
545              
546              
547 226         701 my $method_vars = _assert_symmetric_algorithm($method);
548              
549 226         789 my $ivsize = $method_vars->{ivsize};
550 226         531 my $keysize = $method_vars->{keysize};
551              
552 226         1480 my $iv = random_bytes($ivsize);
553 226         5834 ${$key} = random_bytes($keysize);
  226         2403  
554              
555 226 100       1349 if ($method =~ /gcm/ ){
556             my $gcm
557 96         315 = Crypt::AuthEnc::GCM->new($method_vars->{modename}, ${$key}, $iv);
  96         30791  
558              
559             # Note that GCM support for additional authentication
560             # data is not used in the XML specification.
561 96         1560 my $encrypted = $gcm->encrypt_add($data);
562 96         550 my $tag = $gcm->encrypt_done();
563              
564 96         1245 return $iv . $encrypted . $tag;
565             }
566              
567 130         1826 my $cbc = Crypt::Mode::CBC->new($method_vars->{modename}, 0);
568             # XML Encryption 5.2 Block Encryption Algorithms
569             # The resulting cipher text is prefixed by the IV.
570 130         15277 $data = $self->_add_padding($data, $ivsize);
571 130         338 return $iv . $cbc->encrypt($data, ${$key}, $iv);
  130         770  
572             }
573              
574             sub _decrypt {
575 239     239   463 my $sub = shift;
576 239         449 my $decrypt;
577 239         379 eval { $decrypt = $sub->() };
  239         560  
578 239 100       10191 return $decrypt unless $@;
579 1         21 return;
580             }
581              
582             sub _decrypt_key {
583 239     239   469 my $self = shift;
584 239         559 my $key = shift;
585 239         405 my $algo = shift;
586 239         493 my $digest_name = shift;
587 239         503 my $oaep = shift;
588 239         446 my $mgf = shift;
589              
590 239 100       976 if ($algo eq 'http://www.w3.org/2001/04/xmlenc#rsa-1_5') {
591 12     12   87 return _decrypt(sub{$self->{key_obj}->decrypt($key, 'v1.5')});
  12         579748  
592             }
593              
594 227 100       949 if ($algo eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') {
595             return _decrypt(
596             sub {
597 52 50   52   545 if ($CryptX::VERSION lt 0.081) {
598             #print "Caller: _decrypt_key rsa-oaep-mgf1p\n";
599             $self->{key_obj}->decrypt(
600 0   0     0 $key, 'oaep',
      0        
601             #$self->_getOAEPAlgorithm($mgf),
602             $digest_name // 'SHA1',
603             $oaep // '',
604             );
605             } else {
606             #print "Caller: _decrypt_key rsa-oaep-mgf1p\n";
607             #print "digest_name: ", $digest_name, "\n";
608             $self->{key_obj}->decrypt(
609 52   50     2635157 $key, 'oaep',
      100        
      100        
610             $mgf // 'SHA1',
611             $oaep // '',
612             $digest_name // 'SHA1',
613             );
614             }
615             }
616 52         410 );
617             }
618              
619 175 50       610 if ($algo eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
620             return _decrypt(
621             sub {
622 175 50   175   1568 if ($CryptX::VERSION lt 0.081) {
623             $self->{key_obj}->decrypt(
624 0   0     0 $key, 'oaep',
625             $self->_getOAEPAlgorithm($mgf),
626             $oaep // '',
627             );
628             } else {
629             $self->{key_obj}->decrypt(
630 175   50     798 $key, 'oaep',
      50        
631             $self->_getOAEPAlgorithm($mgf),
632             $oaep // '',
633             $digest_name // '',
634             );
635             }
636             }
637 175         1463 );
638             }
639              
640 0         0 die "Unsupported algorithm for key decryption: $algo";
641             }
642              
643             sub _EncryptKey {
644 226     226   507 my $self = shift;
645 226         469 my $keymethod = shift;
646 226         438 my $key = shift;
647              
648 226         568 my $rsa_pub = $self->{cert_obj};
649              
650             # FIXME: this could use some refactoring and some simplfication
651 226 100       1445 if ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-1_5') {
    100          
    50          
652 7         13 ${$key} = $rsa_pub->encrypt(${$key}, 'v1.5');
  7         28  
  7         7800  
653             }
654             elsif ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') {
655 44 50       475 if ($CryptX::VERSION lt 0.081) {
656 0         0 ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params});
  0         0  
  0         0  
657             } else {
658             my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
659 44 100 66     450 $self->_getParamsAlgorithm($self->{oaep_label_hash}) : 'SHA1';
660 44         105 ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}, $oaep_label_hash);
  44         254  
  44         65087  
661             }
662             }
663             elsif ($keymethod eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
664             my $mgf_hash = defined $self->{oaep_mgf_alg} ?
665 175 50       1019 $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}) : undef;
666 175 50       1743 if ($CryptX::VERSION lt 0.081) {
667 0         0 ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params});
  0         0  
  0         0  
668             } else {
669             my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
670 175 50 33     1563 $self->_getParamsAlgorithm($self->{oaep_label_hash}) : $mgf_hash;
671 175         410 ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params}, $oaep_label_hash);
  175         1075  
  175         227847  
672             }
673             } else {
674 0         0 die "Unsupported algorithm for key encyption $keymethod}";
675             }
676              
677 226 50       1333 print "Encrypted key: ", encode_base64(${$key}) if $DEBUG;
  0         0  
678             }
679              
680             sub _setEncryptedData {
681 226     226   492 my $self = shift;
682 226         442 my $context = shift;
683 226         411 my $xpc = shift;
684 226         526 my $cipherdata = shift;
685              
686 226         731 my $node = $xpc->findnodes('xenc:EncryptedData/xenc:CipherData/xenc:CipherValue', $context);
687              
688 226         11858 $node->[0]->removeChildNodes();
689 226         1102 $node->[0]->appendText($cipherdata);
690 226         834 return $context;
691             }
692              
693             sub _setKeyEncryptedData {
694 226     226   449 my $self = shift;
695 226         411 my $context = shift;
696 226         414 my $xpc = shift;
697 226         500 my $cipherdata = shift;
698              
699 226         376 my $node;
700              
701 226 50       901 if ($xpc->findvalue('dsig:KeyInfo/dsig:RetrievalMethod/@Type', $context)
702             eq 'http://www.w3.org/2001/04/xmlenc#EncryptedKey')
703             {
704 0         0 my $id = $xpc->findvalue('dsig:KeyInfo/dsig:RetrievalMethod/@URI', $context);
705 0         0 $id =~ s/#//g;
706              
707 0         0 my $keyinfo = $xpc->find('//*[@Id=\''. $id . '\']', $context);
708 0 0       0 if (! $keyinfo ) {
709 0         0 die "Unable to find EncryptedKey";
710             }
711              
712 0         0 $node = $keyinfo->[0]->findnodes('//xenc:EncryptedKey/xenc:CipherData', $context)->[0];
713             } else {
714 226         20747 $node = $xpc->findnodes('//dsig:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue')->[0];
715             }
716 226         11859 $node->removeChildNodes();
717 226         1900 $node->appendText($cipherdata);
718             }
719              
720             sub _remove_padding {
721 142     142   8630 my $self = shift;
722 142         315 my $padded = shift;
723              
724 142         298 my $len = length $padded;
725 142         448 my $padlen = ord substr $padded, $len - 1;
726 142         1179 return substr $padded, 0, $len - $padlen;
727             }
728              
729             sub _add_padding {
730 130     130   287 my $self = shift;
731 130         245 my $data = shift;
732 130         263 my $blksize = shift;
733              
734 130         452 my $len = length $data;
735 130         406 my $padlen = $blksize - ($len % $blksize);
736 130         230 my @pad;
737 130         240 my $n = 0;
738 130         483 while ($n < $padlen -1 ) {
739 130         823 $pad[$n] = 176 + int(rand(80));
740 130         406 $n++;
741             }
742              
743 130         1195 return $data . pack ("C*", @pad, $padlen);
744             }
745              
746             ##
747             ## _trim($string)
748             ##
749             ## Arguments:
750             ## $string: string String to remove whitespace
751             ##
752             ## Returns: string Trimmed String
753             ##
754             ## Trim the whitespace from the begining and end of the string
755             ##
756             sub _trim {
757 227     227   583 my $string = shift;
758 227         1363 $string =~ s/^\s+//;
759 227         2921 $string =~ s/\s+$//;
760 227         1059 return $string;
761             }
762              
763             ##
764             ## _load_key($file)
765             ##
766             ## Arguments: $self->{ key }
767             ##
768             ## Returns: nothing
769             ##
770             ## Load the key and process it acording to its headers
771             ##
772             sub _load_key {
773 240     240   580 my $self = shift;
774 240         744 my $file = $self->{ key };
775              
776 240 50       21116 if ( open my $KEY, '<', $file ) {
777 240         1021 my $text = '';
778 240         1678 local $/ = undef;
779 240         8357 $text = <$KEY>;
780 240         3481 close $KEY;
781 240 50       4164 if ( $text =~ m/BEGIN ([DR]SA) PRIVATE KEY/ ) {
    50          
    50          
    0          
782 0         0 my $key_used = $1;
783              
784 0 0       0 if ( $key_used eq 'RSA' ) {
785 0         0 $self->_load_rsa_key( $text );
786             }
787             else {
788 0         0 $self->_load_dsa_key( $text );
789             }
790              
791 0         0 return 1;
792             } elsif ( $text =~ m/BEGIN EC PRIVATE KEY/ ) {
793 0         0 $self->_load_ecdsa_key( $text );
794             } elsif ( $text =~ m/BEGIN PRIVATE KEY/ ) {
795 240         1200 $self->_load_rsa_key( $text );
796             } elsif ($text =~ m/BEGIN CERTIFICATE/) {
797 0         0 $self->_load_x509_key( $text );
798             }
799             else {
800 0         0 confess "Could not detect type of key $file.";
801             }
802             }
803             else {
804 0         0 confess "Could not load key $file: $!";
805             }
806              
807 240         1907 return;
808             }
809              
810             ##
811             ## _load_rsa_key($key_text)
812             ##
813             ## Arguments:
814             ## $key_text: string RSA Private Key as String
815             ##
816             ## Returns: nothing
817             ##
818             ## Populate:
819             ## self->{KeyInfo}
820             ## self->{key_obj}
821             ## self->{key_type}
822             ##
823             sub _load_rsa_key {
824 240     240   575 my $self = shift;
825 240         848 my ($key_text) = @_;
826              
827 240         751 eval {
828 240         3184 require Crypt::PK::RSA;
829             };
830 240 50       962 confess "Crypt::PK::RSA needs to be installed so that we can handle RSA keys." if $@;
831              
832 240         3527 my $rsaKey = Crypt::PK::RSA->new(\$key_text );
833              
834 240 50       151555 if ( $rsaKey ) {
835 240         1047 $self->{ key_obj } = $rsaKey;
836 240         867 $self->{ key_type } = 'rsa';
837              
838 240 50       1154 if (!$self->{ x509 }) {
839 240         193297 my $keyhash = $rsaKey->key2hash();
840              
841 240         4554 $self->{KeyInfo} = "
842            
843            
844             $keyhash->{N}
845             $keyhash->{d}
846            
847            
848             ";
849             }
850             }
851             else {
852 0         0 confess "did not get a new Crypt::PK::RSA object";
853             }
854             }
855              
856             ##
857             ## _load_x509_key($key_text)
858             ##
859             ## Arguments:
860             ## $key_text: string RSA Private Key as String
861             ##
862             ## Returns: nothing
863             ##
864             ## Populate:
865             ## self->{key_obj}
866             ## self->{key_type}
867             ##
868             sub _load_x509_key {
869 0     0   0 my $self = shift;
870 0         0 my $key_text = shift;
871              
872 0         0 eval {
873 0         0 require Crypt::OpenSSL::X509;
874             };
875 0 0       0 confess "Crypt::OpenSSL::X509 needs to be installed so that we
876             can handle X509 Certificates." if $@;
877              
878 0         0 my $x509Key = Crypt::OpenSSL::X509->new_private_key( $key_text );
879              
880 0 0       0 if ( $x509Key ) {
881 0         0 $x509Key->use_pkcs1_padding();
882 0         0 $self->{ key_obj } = $x509Key;
883 0         0 $self->{key_type} = 'x509';
884             }
885             else {
886 0         0 confess "did not get a new Crypt::OpenSSL::X509 object";
887             }
888             }
889              
890             ##
891             ## _load_cert_file()
892             ##
893             ## Arguments: none
894             ##
895             ## Returns: nothing
896             ##
897             ## Read the file name from $self->{ cert } and
898             ## Populate:
899             ## self->{key_obj}
900             ## $self->{KeyInfo}
901             ##
902             sub _load_cert_file {
903 227     227   575 my $self = shift;
904              
905 227         2862 eval {
906 227         4200 require Crypt::OpenSSL::X509;
907             };
908              
909 227 50       111103 die "Crypt::OpenSSL::X509 needs to be installed so that we can handle X509 certs.\n" if $@;
910              
911 227         809 my $file = $self->{ cert };
912 227 50       7028 if (!-r $file) {
913 0         0 die "Could not find certificate file $file";
914             }
915 227 50       10221 open my $CERT, '<', $file or die "Unable to open $file\n";
916 227         808 my $text = '';
917 227         1450 local $/ = undef;
918 227         5219 $text = <$CERT>;
919 227         2607 close $CERT;
920              
921 227         2657 my $cert = Crypt::PK::RSA->new(\$text);
922 227 50       110114 die "Could not load certificate from $file" unless $cert;
923              
924 227         844 $self->{ cert_obj } = $cert;
925 227         1258 my $cert_text = $cert->export_key_pem('public_x509');
926 227         60976 $cert_text =~ s/-----[^-]*-----//gm;
927 227         1160 $self->{KeyInfo} = "\n"._trim($cert_text)."\n";
928 227         2079 return;
929             }
930              
931             sub _create_encrypted_data_xml {
932 226     226   445 my $self = shift;
933              
934 226         585 local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };
935 226         3208 my $doc = XML::LibXML::Document->new();
936              
937 226         572 my $xencns = 'http://www.w3.org/2001/04/xmlenc#';
938 226         539 my $dsigns = 'http://www.w3.org/2000/09/xmldsig#';
939 226         408 my $xenc11ns = 'http://www.w3.org/2009/xmlenc11#';
940              
941 226         1508 my $encdata = $self->_create_node($doc, $xencns, $doc, 'xenc:EncryptedData',
942             {
943             Type => 'http://www.w3.org/2001/04/xmlenc#Element',
944             }
945             );
946              
947 226         5432 $doc->setDocumentElement ($encdata);
948              
949             my $encmethod = $self->_create_node(
950             $doc,
951             $xencns,
952             $encdata,
953             'xenc:EncryptionMethod',
954             {
955             Algorithm => $self->{data_enc_method},
956             }
957 226         10645 );
958              
959 226         3225 my $keyinfo = $self->_create_node(
960             $doc,
961             $dsigns,
962             $encdata,
963             'dsig:KeyInfo',
964             );
965              
966 226         2936 my $enckey = $self->_create_node(
967             $doc,
968             $xencns,
969             $keyinfo,
970             'xenc:EncryptedKey',
971             );
972              
973             my $kencmethod = $self->_create_node(
974             $doc,
975             $xencns,
976             $enckey,
977             'xenc:EncryptionMethod',
978             {
979             Algorithm => $self->{key_transport},
980             }
981 226         3167 );
982              
983 226 100 100     3714 if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' ||
      100        
984             $self->{key_transport} eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' &&
985             $self->{oaep_label_hash}) {
986             my $digestmethod = $self->_create_node(
987             $doc,
988             $dsigns,
989             $kencmethod,
990             'dsig:DigestMethod',
991             {
992             Algorithm => $self->{oaep_label_hash},
993             }
994 210         1064 );
995             };
996              
997 226 100       2765 if ($self->{'oaep_params'} ne '') {
998 211         3003 my $oaep_params = $self->_create_node(
999             $doc,
1000             $xencns,
1001             $kencmethod,
1002             'xenc:OAEPparams',
1003             );
1004             };
1005              
1006 226 50 66     2752 if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' &&
1007             $self->{oaep_mgf_alg}) {
1008             my $oaepmethod = $self->_create_node(
1009             $doc,
1010             $xenc11ns,
1011             $kencmethod,
1012             'xenc11:MGF',
1013             {
1014             Algorithm => $self->{oaep_mgf_alg},
1015             }
1016 175         3388 );
1017             };
1018              
1019 226         2687 my $keyinfo2 = $self->_create_node(
1020             $doc,
1021             $dsigns,
1022             $enckey,
1023             'dsig:KeyInfo',
1024             );
1025              
1026 226 100       2938 if (defined $self->{key_name}) {
1027 224         692 my $keyname = $self->_create_node(
1028             $doc,
1029             $dsigns,
1030             $keyinfo2,
1031             'dsig:KeyName',
1032             );
1033             };
1034              
1035 226         2969 my $keycipherdata = $self->_create_node(
1036             $doc,
1037             $xencns,
1038             $enckey,
1039             'xenc:CipherData',
1040             );
1041              
1042 226         2674 my $keyciphervalue = $self->_create_node(
1043             $doc,
1044             $xencns,
1045             $keycipherdata,
1046             'xenc:CipherValue',
1047             );
1048              
1049 226         2734 my $cipherdata = $self->_create_node(
1050             $doc,
1051             $xencns,
1052             $encdata,
1053             'xenc:CipherData',
1054             );
1055              
1056 226         2797 my $ciphervalue = $self->_create_node(
1057             $doc,
1058             $xencns,
1059             $cipherdata,
1060             'xenc:CipherValue',
1061             );
1062              
1063 226         2640 return $doc;
1064             }
1065              
1066             sub _create_node {
1067 3080     3080   8348 my $self = shift;
1068 3080         4288 my $doc = shift;
1069 3080         4723 my $nsuri = shift;
1070 3080         4317 my $parent = shift;
1071 3080         4422 my $name = shift;
1072 3080         6373 my $attributes = shift;
1073              
1074 3080         18202 my $node = $doc->createElementNS ($nsuri, $name);
1075 3080         8096 for (keys %$attributes) {
1076             $node->addChild (
1077             $doc->createAttribute (
1078             #$node->setAttribute (
1079 1063         13487 $_ => $attributes->{$_}
1080             )
1081             );
1082             }
1083 3080         17719 $parent->addChild($node);
1084             }
1085              
1086             1;
1087              
1088             __END__