diff --git a/lib/XML/Enc.pm b/lib/XML/Enc.pm index 26aadf5..ea5deb7 100644 --- a/lib/XML/Enc.pm +++ b/lib/XML/Enc.pm @@ -107,13 +107,14 @@ sub _assert_encryption_digest { state $ENC_DIGEST = { 'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1', 'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256', + 'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224', + 'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384', + 'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512', }; - die "Unsupported encryption digest algo $algo" unless $ENC_DIGEST->{ $algo }; return $ENC_DIGEST->{ $algo }; } - =head2 new( ... ) Constructor. Creates an instance of the XML::Enc object @@ -198,6 +199,39 @@ Used in encryption. Optional. Default method: mgf1sha1 =back +=item B + +Specify the OAEPparams value to use as part of the mask generation function (MGF). +It is optional but can be specified for rsa-oaep and rsa-oaep-mgf1p EncryptionMethods. + +It is base64 encoded and stored in the XML as OAEPparams. + +If specified you MAY specify the oaep_label_hash that should be used. You should note +that not all implementations support an oaep_label_hash that differs from that of the +MGF specified in the xenc11:MGF element or the default MGF1 with SHA1. + +The oaep_label_hash is stored in the DigestMethod child element of the EncryptionMethod. + +=item B + +Specify the Hash Algorithm to use for the rsa-oaep label as specified by oaep_params. + +The default is sha1. Supported algorithms are: + +=over + +=item * L + +=item * L + +=item * L + +=item * L + +=item * L + +=back + =cut sub new { @@ -225,8 +259,12 @@ sub new { my $key_method = exists($params->{'key_transport'}) ? $params->{'key_transport'} : 'rsa-oaep-mgf1p '; $self->{'key_transport'} = $self->_setKeyEncryptionMethod($key_method); - my $oaep_mgf_alg = exists($params->{'oaep_mgf_alg'}) ? $params->{'oaep_mgf_alg'} : 'http://www.w3.org/2009/xmlenc11#mgf1sha1'; - $self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($oaep_mgf_alg); + if (exists $params->{'oaep_mgf_alg'}) { + $self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($params->{'oaep_mgf_alg'}); + } + if (exists $params->{'oaep_label_hash'} ) { + $self->{'oaep_label_hash'} = $self->_setOAEPDigest($params->{'oaep_label_hash'}); + } $self->{'oaep_params'} = exists($params->{'oaep_params'}) ? $params->{'oaep_params'} : ''; @@ -444,6 +482,23 @@ sub _get_oaep_params { return; } +sub _get_oaep_hash { + my $self = shift; + my $node = shift; + my $xpc = shift; + + print "==================================================\n"; + print $node, "\n"; + print "--------------------------------------------------\n"; + print $xpc, "\n"; + my $value = $xpc->findvalue('./xenc:EncryptionMethod/dsig:DigestMethod/@Algorithm', $node); + print "--------------------------------------------------\n"; + print $value, "\n"; + print "==================================================\n"; + return _assert_encryption_digest($value) if $value; + return; +} + sub _get_digest_method { my $self = shift; my $node = shift; @@ -576,6 +631,36 @@ sub _getOAEPAlgorithm { return $OAEPAlgorithm->{$method} // 'SHA1'; } +sub _setOAEPDigest { + my $self = shift; + my $method = shift; + + state $OAEPDigest = { + 'sha1' => 'http://www.w3.org/2000/09/xmldsig#sha1', + 'sha224' => 'http://www.w3.org/2001/04/xmldsig-more#sha224', + 'sha256' => 'http://www.w3.org/2001/04/xmlenc#sha256', + 'sha384' => 'http://www.w3.org/2001/04/xmldsig-more#sha384', + 'sha512' => 'http://www.w3.org/2001/04/xmlenc#sha512', + }; + + return $OAEPDigest->{$method} // $OAEPDigest->{'sha256'}; +} + +sub _getParamsAlgorithm { + my $self = shift; + my $method = shift; + + state $ParamsAlgorithm = { + 'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1', + 'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224', + 'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256', + 'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384', + 'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512', + }; + + return $ParamsAlgorithm->{$method} // $ParamsAlgorithm->{'http://www.w3.org/2000/09/xmldsig#sha1'}; +} + sub _setKeyEncryptionMethod { my $self = shift; my $method = shift; @@ -681,11 +766,24 @@ sub _decrypt_key { if ($algo eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') { return _decrypt( sub { - $self->{key_obj}->decrypt( - $key, 'oaep', - $digest_name // 'SHA1', - $oaep // '' - ); + if ($CryptX::VERSION le 0.077) { + #print "Caller: _decrypt_key rsa-oaep-mgf1p\n"; + $self->{key_obj}->decrypt( + $key, 'oaep', + #$self->_getOAEPAlgorithm($mgf), + $digest_name // 'SHA1', + $oaep // '', + ); + } else { + #print "Caller: _decrypt_key rsa-oaep-mgf1p\n"; + #print "digest_name: ", $digest_name, "\n"; + $self->{key_obj}->decrypt( + $key, 'oaep', + $mgf // 'SHA1', + $oaep // '', + $digest_name // 'SHA1', + ); + } } ); } @@ -693,11 +791,20 @@ sub _decrypt_key { if ($algo eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') { return _decrypt( sub { - $self->{key_obj}->decrypt( - $key, 'oaep', - $self->_getOAEPAlgorithm($mgf), - $oaep // '', - ); + if ($CryptX::VERSION le 0.077) { + $self->{key_obj}->decrypt( + $key, 'oaep', + $self->_getOAEPAlgorithm($mgf), + $oaep // '', + ); + } else { + $self->{key_obj}->decrypt( + $key, 'oaep', + $self->_getOAEPAlgorithm($mgf), + $oaep // '', + $digest_name // '', + ); + } } ); } @@ -716,10 +823,35 @@ sub _EncryptKey { ${$key} = $rsa_pub->encrypt(${$key}, 'v1.5'); } elsif ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') { - ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}); + if ($CryptX::VERSION le 0.077) { + ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}); + } else { + my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ? + $self->_getParamsAlgorithm($self->{oaep_label_hash}) : 'SHA1'; + my $mgf_hash = defined $self->{oaep_mgf_alg} ? + $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}) : undef; + #print "Y_mgf_hash: ", $mgf_hash, "\n"; + #print "Xoaep_label_hash: ", $oaep_label_hash, "\n"; + #print "Xoaep_params: ", $self->{oaep_params}, "\n"; + ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}, $oaep_label_hash); + #print "Got Here\n"; + } } elsif ($keymethod eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') { - ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}), $self->{oaep_params}); + #FIXME + my $mgf_hash = defined $self->{oaep_mgf_alg} ? + $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}) : undef; + my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ? + $self->_getParamsAlgorithm($self->{oaep_label_hash}) : $mgf_hash; + #print "Y_mgf_hash: ", $mgf_hash, "\n"; + #print "Y_oaep_label_hash: ", $self->{oaep_label_hash}, "\n"; + #print "Y_oaep_label_hash: ", $oaep_label_hash, "\n"; + #print "Y_oaep_params: ", $self->{oaep_params}, "\n"; + if ($CryptX::VERSION le 0.077) { + ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params}); + } else { + ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params}, $oaep_label_hash); + } } else { die "Unsupported algorithm for key encyption $keymethod}"; } @@ -1030,6 +1162,20 @@ sub _create_encrypted_data_xml { } ); + if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' || + $self->{key_transport} eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' && + $self->{oaep_label_hash}) { + my $digestmethod = $self->_create_node( + $doc, + $dsigns, + $kencmethod, + 'dsig:DigestMethod', + { + Algorithm => $self->{oaep_label_hash}, + } + ); + }; + if ($self->{'oaep_params'} ne '') { my $oaep_params = $self->_create_node( $doc, @@ -1039,7 +1185,8 @@ sub _create_encrypted_data_xml { ); }; - if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') { + if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' && + $self->{oaep_mgf_alg}) { my $oaepmethod = $self->_create_node( $doc, $xenc11ns, diff --git a/t/06-test-encryption-methods.t b/t/06-test-encryption-methods.t index a1b922f..2dd3b48 100644 --- a/t/06-test-encryption-methods.t +++ b/t/06-test-encryption-methods.t @@ -1,6 +1,6 @@ use strict; use warnings; -use Test::More tests => 126; +use Test::More tests => 896; use Test::Lib; use Test::XML::Enc; use XML::Enc; @@ -15,10 +15,12 @@ XML my @key_methods = qw/rsa-1_5 rsa-oaep-mgf1p/; my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc aes128-gcm aes192-gcm aes256-gcm/; -my @oaep_mgf_algs = qw/mgf1sha1 mgf1sha224 mgf1sha256 mgf1sha384 mgf1sha512/; +my @oaep_mgf_algs = qw/rsa-oaep-mgf1p mgf1sha1 mgf1sha224 mgf1sha256 mgf1sha384 mgf1sha512/; +my @oaep_label_hashes = qw/sha1 sha224 sha256 sha384 sha512/; my $xmlsec = get_xmlsec_features(); my $lax_key_search = $xmlsec->{lax_key_search} ? '--lax-key-search': ''; +my $cryptx = get_cryptx_features(); foreach my $km (@key_methods) { foreach my $dm (@data_methods) { @@ -39,10 +41,6 @@ foreach my $km (@key_methods) { SKIP: { skip "xmlsec1 not installed", 2 unless $xmlsec->{installed}; - my $version; - if (`xmlsec1 version` =~ m/(\d+\.\d+\.\d+)/) { - $version = $1; - }; skip "xmlsec version 1.2.27 minimum for GCM", 2 if ! $xmlsec->{aes_gcm}; ok( open XML, '>', 'tmp.xml' ); print XML $encrypted; @@ -56,22 +54,45 @@ foreach my $km (@key_methods) { } foreach my $om (@oaep_mgf_algs) { - foreach my $dm (@data_methods) { - my $encrypter = XML::Enc->new( - { - key => 't/sign-private.pem', - cert => 't/sign-certonly.pem', - data_enc_method => $dm, - key_transport => 'rsa-oaep', - oaep_mgf_alg => $om, - no_xml_declaration => 1 + foreach my $omdig (@oaep_label_hashes) { + SKIP: { + if (! $cryptx->{oaem_mgf_digest} && ($om ne $omdig)) { + my $skip = (scalar @data_methods) * 4; + skip "CryptX $cryptx->{version} does not support rsa-oaep MGF: $om and digest $omdig", $skip; } - ); - my $encrypted = $encrypter->encrypt($xml); - like($encrypted, qr/EncryptedData/, "Successfully Encrypted: Key Method 'rsa-oaep' with $om Data Method $dm"); + my $km = ( $om eq 'rsa-oaep-mgf1p') ? 'rsa-oaep-mgf1p' : 'rsa-oaep'; + foreach my $dm (@data_methods) { + my $encrypter = XML::Enc->new( + { + key => 't/sign-private.pem', + cert => 't/sign-certonly.pem', + data_enc_method => $dm, + key_transport => $km, + oaep_mgf_alg => $om, + oaep_label_hash => $omdig, + oaep_params => 'encrypt', + no_xml_declaration => 1, + } + ); - like($encrypter->decrypt($encrypted), qr/XML-SIG_1/, "Successfully Decrypted with XML::Enc"); + my $encrypted = $encrypter->encrypt($xml); + ok($encrypted =~ /EncryptedData/, "Successful Encrypted: Key Method:$km MGF:$om, param:$omdig Data Method:$dm"); + + SKIP: { + skip "xmlsec1 not installed", 2 unless $xmlsec->{installed}; + skip "xmlsec version 1.2.27 minimum for GCM", 2 if ! $xmlsec->{aes_gcm}; + ok( open XML, '>', "$km-$om-$omdig-$dm-tmp.xml" ); + print XML $encrypted; + close XML; + my $verify_response = `xmlsec1 --decrypt $lax_key_search --privkey-pem t/sign-private.pem $km-$om-$omdig-$dm-tmp.xml 2>&1`; + ok( $verify_response =~ m/XML-SIG_1/, "Successfully decrypted with xmlsec1" ) + or warn "calling xmlsec1 failed: '$verify_response'\n"; + unlink "$km-$om-$omdig-$dm-tmp.xml"; + } + ok($encrypter->decrypt($encrypted) =~ /XML-SIG_1/, "Successfully Decrypted with XML::Enc"); + } + } } } done_testing; diff --git a/t/lib/Test/XML/Enc/Util.pm b/t/lib/Test/XML/Enc/Util.pm index eafe539..02b6b40 100644 --- a/t/lib/Test/XML/Enc/Util.pm +++ b/t/lib/Test/XML/Enc/Util.pm @@ -9,6 +9,7 @@ our @ISA = qw(Exporter); our @EXPORT = qw( get_xmlsec_features get_openssl_features + get_cryptx_features ); our @EXPORT_OK; @@ -87,6 +88,35 @@ sub get_openssl_features { return \%openssl; } +######################################################################### +# get_cryptx_features +# +# Parameter: none +# +# Returns a hash of the version and any features that are needed +# if proper the version is installed +# +# Response: hash +# +# %features = ( +# version => '0.077', +# oaem_mgf_digest => 0, +# ); +########################################################################## +sub get_cryptx_features { + + require CryptX; + + my $version = $CryptX::VERSION; + + my %cryptx = ( + version => $version, + oaem_mgf_digest => ($version gt '0.080') ? 1 : 0, + ); + + return \%cryptx; +} + 1; __END__