Skip to content

Commit 02a30bd

Browse files
committed
Fix MGF support
1 parent 0d24262 commit 02a30bd

File tree

6 files changed

+240
-36
lines changed

6 files changed

+240
-36
lines changed

Makefile.PL

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ my %WriteMakefileArgs = (
3131
},
3232
"TEST_REQUIRES" => {
3333
"Crypt::OpenSSL::Guess" => 0,
34+
"CryptX" => 0,
3435
"Exporter" => 0,
3536
"File::Slurper" => 0,
3637
"File::Which" => 0,
@@ -55,6 +56,7 @@ my %FallbackPrereqs = (
5556
"Crypt::OpenSSL::X509" => 0,
5657
"Crypt::PK::RSA" => 0,
5758
"Crypt::PRNG" => 0,
59+
"CryptX" => 0,
5860
"Exporter" => 0,
5961
"File::Slurper" => 0,
6062
"File::Which" => 0,

README

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,37 @@ METHODS
103103

104104
* mgf1sha512 <http://www.w3.org/2009/xmlenc11#mgf1sha512>
105105

106+
oaep_params
107+
Specify the OAEPparams value to use as part of the mask generation
108+
function (MGF). It is optional but can be specified for rsa-oaep and
109+
rsa-oaep-mgf1p EncryptionMethods.
110+
111+
It is base64 encoded and stored in the XML as OAEPparams.
112+
113+
If specified you MAY specify the oaep_label_hash that should be
114+
used. You should note that not all implementations support an
115+
oaep_label_hash that differs from that of the MGF specified in the
116+
xenc11:MGF element or the default MGF1 with SHA1.
117+
118+
The oaep_label_hash is stored in the DigestMethod child element of
119+
the EncryptionMethod.
120+
121+
oaep_label_hash
122+
Specify the Hash Algorithm to use for the rsa-oaep label as
123+
specified by oaep_params.
124+
125+
The default is sha1. Supported algorithms are:
126+
127+
* sha1 <http://www.w3.org/2000/09/xmldsig#sha1>
128+
129+
* sha224 <http://www.w3.org/2001/04/xmldsig-more#sha224>
130+
131+
* sha256 <http://www.w3.org/2001/04/xmlenc#sha256>
132+
133+
* sha384 <http://www.w3.org/2001/04/xmldsig-more#sha384>
134+
135+
* sha512 <http://www.w3.org/2001/04/xmlenc#sha512>
136+
106137
decrypt( ... )
107138
Main decryption function.
108139

@@ -121,7 +152,7 @@ AUTHOR
121152
Timothy Legge <timlegge@cpan.org>
122153

123154
COPYRIGHT AND LICENSE
124-
This software is copyright (c) 2023 by TImothy Legge.
155+
This software is copyright (c) 2024 by TImothy Legge.
125156

126157
This is free software; you can redistribute it and/or modify it under
127158
the same terms as the Perl 5 programming language system itself.

cpanfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ requires "warnings" => "0";
1616

1717
on 'test' => sub {
1818
requires "Crypt::OpenSSL::Guess" => "0";
19+
requires "CryptX" => "0";
1920
requires "Exporter" => "0";
2021
requires "File::Slurper" => "0";
2122
requires "File::Which" => "0";

lib/XML/Enc.pm

Lines changed: 135 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,10 @@ sub _assert_encryption_digest {
107107
state $ENC_DIGEST = {
108108
'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
109109
'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
110+
'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
111+
'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
112+
'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
110113
};
111-
112114
die "Unsupported encryption digest algo $algo" unless $ENC_DIGEST->{ $algo };
113115
return $ENC_DIGEST->{ $algo };
114116
}
@@ -196,6 +198,37 @@ Used in encryption. Optional. Default method: mgf1sha1
196198
197199
=back
198200
201+
=item B<oaep_params>
202+
203+
Specify the OAEPparams value to use as part of the mask generation function (MGF).
204+
It is optional but can be specified for rsa-oaep and rsa-oaep-mgf1p EncryptionMethods.
205+
206+
It is base64 encoded and stored in the XML as OAEPparams.
207+
208+
If specified you MAY specify the oaep_label_hash that should be used. You should note
209+
that not all implementations support an oaep_label_hash that differs from that of the
210+
MGF specified in the xenc11:MGF element or the default MGF1 with SHA1.
211+
212+
The oaep_label_hash is stored in the DigestMethod child element of the EncryptionMethod.
213+
214+
=item B<oaep_label_hash>
215+
216+
Specify the Hash Algorithm to use for the rsa-oaep label as specified by oaep_params.
217+
218+
The default is sha1. Supported algorithms are:
219+
220+
=over
221+
222+
=item * L<sha1|http://www.w3.org/2000/09/xmldsig#sha1>
223+
224+
=item * L<sha224|http://www.w3.org/2001/04/xmldsig-more#sha224>
225+
226+
=item * L<sha256|http://www.w3.org/2001/04/xmlenc#sha256>
227+
228+
=item * L<sha384|http://www.w3.org/2001/04/xmldsig-more#sha384>
229+
230+
=item * L<sha512|http://www.w3.org/2001/04/xmlenc#sha512>
231+
199232
=back
200233
201234
=cut
@@ -225,8 +258,12 @@ sub new {
225258
my $key_method = exists($params->{'key_transport'}) ? $params->{'key_transport'} : 'rsa-oaep-mgf1p ';
226259
$self->{'key_transport'} = $self->_setKeyEncryptionMethod($key_method);
227260

228-
my $oaep_mgf_alg = exists($params->{'oaep_mgf_alg'}) ? $params->{'oaep_mgf_alg'} : 'http://www.w3.org/2009/xmlenc11#mgf1sha1';
229-
$self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($oaep_mgf_alg);
261+
if (exists $params->{'oaep_mgf_alg'}) {
262+
$self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($params->{'oaep_mgf_alg'});
263+
}
264+
if (exists $params->{'oaep_label_hash'} ) {
265+
$self->{'oaep_label_hash'} = $self->_setOAEPDigest($params->{'oaep_label_hash'});
266+
}
230267

231268
$self->{'oaep_params'} = exists($params->{'oaep_params'}) ? $params->{'oaep_params'} : '';
232269

@@ -576,6 +613,36 @@ sub _getOAEPAlgorithm {
576613
return $OAEPAlgorithm->{$method} // 'SHA1';
577614
}
578615

616+
sub _setOAEPDigest {
617+
my $self = shift;
618+
my $method = shift;
619+
620+
state $OAEPDigest = {
621+
'sha1' => 'http://www.w3.org/2000/09/xmldsig#sha1',
622+
'sha224' => 'http://www.w3.org/2001/04/xmldsig-more#sha224',
623+
'sha256' => 'http://www.w3.org/2001/04/xmlenc#sha256',
624+
'sha384' => 'http://www.w3.org/2001/04/xmldsig-more#sha384',
625+
'sha512' => 'http://www.w3.org/2001/04/xmlenc#sha512',
626+
};
627+
628+
return $OAEPDigest->{$method} // $OAEPDigest->{'sha256'};
629+
}
630+
631+
sub _getParamsAlgorithm {
632+
my $self = shift;
633+
my $method = shift;
634+
635+
state $ParamsAlgorithm = {
636+
'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
637+
'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
638+
'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
639+
'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
640+
'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
641+
};
642+
643+
return $ParamsAlgorithm->{$method} // $ParamsAlgorithm->{'http://www.w3.org/2000/09/xmldsig#sha1'};
644+
}
645+
579646
sub _setKeyEncryptionMethod {
580647
my $self = shift;
581648
my $method = shift;
@@ -681,23 +748,45 @@ sub _decrypt_key {
681748
if ($algo eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') {
682749
return _decrypt(
683750
sub {
684-
$self->{key_obj}->decrypt(
685-
$key, 'oaep',
686-
$digest_name // 'SHA1',
687-
$oaep // ''
688-
);
751+
if ($CryptX::VERSION le 0.081) {
752+
#print "Caller: _decrypt_key rsa-oaep-mgf1p\n";
753+
$self->{key_obj}->decrypt(
754+
$key, 'oaep',
755+
#$self->_getOAEPAlgorithm($mgf),
756+
$digest_name // 'SHA1',
757+
$oaep // '',
758+
);
759+
} else {
760+
#print "Caller: _decrypt_key rsa-oaep-mgf1p\n";
761+
#print "digest_name: ", $digest_name, "\n";
762+
$self->{key_obj}->decrypt(
763+
$key, 'oaep',
764+
$mgf // 'SHA1',
765+
$oaep // '',
766+
$digest_name // 'SHA1',
767+
);
768+
}
689769
}
690770
);
691771
}
692772

693773
if ($algo eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
694774
return _decrypt(
695775
sub {
696-
$self->{key_obj}->decrypt(
697-
$key, 'oaep',
698-
$self->_getOAEPAlgorithm($mgf),
699-
$oaep // '',
700-
);
776+
if ($CryptX::VERSION le 0.081) {
777+
$self->{key_obj}->decrypt(
778+
$key, 'oaep',
779+
$self->_getOAEPAlgorithm($mgf),
780+
$oaep // '',
781+
);
782+
} else {
783+
$self->{key_obj}->decrypt(
784+
$key, 'oaep',
785+
$self->_getOAEPAlgorithm($mgf),
786+
$oaep // '',
787+
$digest_name // '',
788+
);
789+
}
701790
}
702791
);
703792
}
@@ -712,14 +801,29 @@ sub _EncryptKey {
712801

713802
my $rsa_pub = $self->{cert_obj};
714803

804+
# FIXME: this could use some refactoring and some simplfication
715805
if ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-1_5') {
716806
${$key} = $rsa_pub->encrypt(${$key}, 'v1.5');
717807
}
718808
elsif ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') {
719-
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params});
809+
if ($CryptX::VERSION le 0.081) {
810+
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params});
811+
} else {
812+
my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
813+
$self->_getParamsAlgorithm($self->{oaep_label_hash}) : 'SHA1';
814+
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}, $oaep_label_hash);
815+
}
720816
}
721817
elsif ($keymethod eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
722-
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}), $self->{oaep_params});
818+
my $mgf_hash = defined $self->{oaep_mgf_alg} ?
819+
$self->_getOAEPAlgorithm($self->{oaep_mgf_alg}) : undef;
820+
if ($CryptX::VERSION le 0.081) {
821+
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params});
822+
} else {
823+
my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
824+
$self->_getParamsAlgorithm($self->{oaep_label_hash}) : $mgf_hash;
825+
${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params}, $oaep_label_hash);
826+
}
723827
} else {
724828
die "Unsupported algorithm for key encyption $keymethod}";
725829
}
@@ -1030,6 +1134,20 @@ sub _create_encrypted_data_xml {
10301134
}
10311135
);
10321136

1137+
if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' ||
1138+
$self->{key_transport} eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' &&
1139+
$self->{oaep_label_hash}) {
1140+
my $digestmethod = $self->_create_node(
1141+
$doc,
1142+
$dsigns,
1143+
$kencmethod,
1144+
'dsig:DigestMethod',
1145+
{
1146+
Algorithm => $self->{oaep_label_hash},
1147+
}
1148+
);
1149+
};
1150+
10331151
if ($self->{'oaep_params'} ne '') {
10341152
my $oaep_params = $self->_create_node(
10351153
$doc,
@@ -1039,7 +1157,8 @@ sub _create_encrypted_data_xml {
10391157
);
10401158
};
10411159

1042-
if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
1160+
if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' &&
1161+
$self->{oaep_mgf_alg}) {
10431162
my $oaepmethod = $self->_create_node(
10441163
$doc,
10451164
$xenc11ns,

t/06-test-encryption-methods.t

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use strict;
22
use warnings;
3-
use Test::More tests => 126;
3+
use Test::More tests => 896;
44
use Test::Lib;
55
use Test::XML::Enc;
66
use XML::Enc;
@@ -15,10 +15,12 @@ XML
1515

1616
my @key_methods = qw/rsa-1_5 rsa-oaep-mgf1p/;
1717
my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc aes128-gcm aes192-gcm aes256-gcm/;
18-
my @oaep_mgf_algs = qw/mgf1sha1 mgf1sha224 mgf1sha256 mgf1sha384 mgf1sha512/;
18+
my @oaep_mgf_algs = qw/rsa-oaep-mgf1p mgf1sha1 mgf1sha224 mgf1sha256 mgf1sha384 mgf1sha512/;
19+
my @oaep_label_hashes = qw/sha1 sha224 sha256 sha384 sha512/;
1920

2021
my $xmlsec = get_xmlsec_features();
2122
my $lax_key_search = $xmlsec->{lax_key_search} ? '--lax-key-search': '';
23+
my $cryptx = get_cryptx_features();
2224

2325
foreach my $km (@key_methods) {
2426
foreach my $dm (@data_methods) {
@@ -39,10 +41,6 @@ foreach my $km (@key_methods) {
3941

4042
SKIP: {
4143
skip "xmlsec1 not installed", 2 unless $xmlsec->{installed};
42-
my $version;
43-
if (`xmlsec1 version` =~ m/(\d+\.\d+\.\d+)/) {
44-
$version = $1;
45-
};
4644
skip "xmlsec version 1.2.27 minimum for GCM", 2 if ! $xmlsec->{aes_gcm};
4745
ok( open XML, '>', 'tmp.xml' );
4846
print XML $encrypted;
@@ -56,22 +54,45 @@ foreach my $km (@key_methods) {
5654
}
5755

5856
foreach my $om (@oaep_mgf_algs) {
59-
foreach my $dm (@data_methods) {
60-
my $encrypter = XML::Enc->new(
61-
{
62-
key => 't/sign-private.pem',
63-
cert => 't/sign-certonly.pem',
64-
data_enc_method => $dm,
65-
key_transport => 'rsa-oaep',
66-
oaep_mgf_alg => $om,
67-
no_xml_declaration => 1
57+
foreach my $omdig (@oaep_label_hashes) {
58+
SKIP: {
59+
if (! $cryptx->{oaem_mgf_digest} && ($om ne $omdig)) {
60+
my $skip = (scalar @data_methods) * 4;
61+
skip "CryptX $cryptx->{version} does not support rsa-oaep MGF: $om and digest $omdig", $skip;
6862
}
69-
);
7063

71-
my $encrypted = $encrypter->encrypt($xml);
72-
like($encrypted, qr/EncryptedData/, "Successfully Encrypted: Key Method 'rsa-oaep' with $om Data Method $dm");
64+
my $km = ( $om eq 'rsa-oaep-mgf1p') ? 'rsa-oaep-mgf1p' : 'rsa-oaep';
65+
foreach my $dm (@data_methods) {
66+
my $encrypter = XML::Enc->new(
67+
{
68+
key => 't/sign-private.pem',
69+
cert => 't/sign-certonly.pem',
70+
data_enc_method => $dm,
71+
key_transport => $km,
72+
oaep_mgf_alg => $om,
73+
oaep_label_hash => $omdig,
74+
oaep_params => 'encrypt',
75+
no_xml_declaration => 1,
76+
}
77+
);
7378

74-
like($encrypter->decrypt($encrypted), qr/XML-SIG_1/, "Successfully Decrypted with XML::Enc");
79+
my $encrypted = $encrypter->encrypt($xml);
80+
ok($encrypted =~ /EncryptedData/, "Successful Encrypted: Key Method:$km MGF:$om, param:$omdig Data Method:$dm");
81+
82+
SKIP: {
83+
skip "xmlsec1 not installed", 2 unless $xmlsec->{installed};
84+
skip "xmlsec version 1.2.27 minimum for GCM", 2 if ! $xmlsec->{aes_gcm};
85+
ok( open XML, '>', "$km-$om-$omdig-$dm-tmp.xml" );
86+
print XML $encrypted;
87+
close XML;
88+
my $verify_response = `xmlsec1 --decrypt $lax_key_search --privkey-pem t/sign-private.pem $km-$om-$omdig-$dm-tmp.xml 2>&1`;
89+
ok( $verify_response =~ m/XML-SIG_1/, "Successfully decrypted with xmlsec1" )
90+
or warn "calling xmlsec1 failed: '$verify_response'\n";
91+
unlink "$km-$om-$omdig-$dm-tmp.xml";
92+
}
93+
ok($encrypter->decrypt($encrypted) =~ /XML-SIG_1/, "Successfully Decrypted with XML::Enc");
94+
}
95+
}
7596
}
7697
}
7798
done_testing;

0 commit comments

Comments
 (0)