Skip to content

Commit

Permalink
Fix MGF support
Browse files Browse the repository at this point in the history
  • Loading branch information
timlegge committed Oct 10, 2023
1 parent 0d24262 commit 753e6fc
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 35 deletions.
163 changes: 147 additions & 16 deletions lib/XML/Enc.pm
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ 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 };
}
Expand Down Expand Up @@ -198,6 +200,39 @@ Used in encryption. Optional. Default method: mgf1sha1
=back
=item B<oaep_params>
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<oaep_label_hash>
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<sha1|http://www.w3.org/2000/09/xmldsig#sha1>
=item * L<sha224|http://www.w3.org/2001/04/xmldsig-more#sha224>
=item * L<sha256|http://www.w3.org/2001/04/xmlenc#sha256>
=item * L<sha384|http://www.w3.org/2001/04/xmldsig-more#sha384>
=item * L<sha512|http://www.w3.org/2001/04/xmlenc#sha512>
=back
=cut

sub new {
Expand Down Expand Up @@ -225,8 +260,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'} : '';

Expand Down Expand Up @@ -576,6 +615,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;
Expand Down Expand Up @@ -681,23 +750,45 @@ 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',
);
}
}
);
}

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 // '',
);
}
}
);
}
Expand All @@ -716,10 +807,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}";
}
Expand Down Expand Up @@ -1030,6 +1146,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,
Expand All @@ -1039,7 +1169,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,
Expand Down
59 changes: 40 additions & 19 deletions t/06-test-encryption-methods.t
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
30 changes: 30 additions & 0 deletions t/lib/Test/XML/Enc/Util.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ our @ISA = qw(Exporter);
our @EXPORT = qw(
get_xmlsec_features
get_openssl_features
get_cryptx_features
);

our @EXPORT_OK;
Expand Down Expand Up @@ -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__
Expand Down

0 comments on commit 753e6fc

Please sign in to comment.