diff --git a/Makefile.PL b/Makefile.PL index 33fb190..820b216 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -17,6 +17,7 @@ my %WriteMakefileArgs = ( "NAME" => "XML::Enc", "PREREQ_PM" => { "Carp" => 0, + "Crypt::AuthEnc::GCM" => 0, "Crypt::Mode::CBC" => 0, "Crypt::OpenSSL::Bignum" => 0, "Crypt::OpenSSL::RSA" => 0, @@ -33,7 +34,7 @@ my %WriteMakefileArgs = ( "File::Which" => 0, "Test::More" => 0 }, - "VERSION" => "0.03", + "VERSION" => "0.04", "test" => { "TESTS" => "t/*.t" } @@ -42,6 +43,7 @@ my %WriteMakefileArgs = ( my %FallbackPrereqs = ( "Carp" => 0, + "Crypt::AuthEnc::GCM" => 0, "Crypt::Mode::CBC" => 0, "Crypt::OpenSSL::Bignum" => 0, "Crypt::OpenSSL::RSA" => 0, diff --git a/README b/README index 44b9870..ef41995 100644 --- a/README +++ b/README @@ -2,7 +2,7 @@ NAME XML::Enc - XML::Enc Encryption Support VERSION - version 0.03 + version 0.04 SYNOPSIS my $decrypter = XML::Enc->new( diff --git a/cpanfile b/cpanfile index 013e3ec..46e584d 100644 --- a/cpanfile +++ b/cpanfile @@ -1,6 +1,7 @@ # Do not edit this file directly. To change prereqs, edit the `dist.ini` file. requires "Carp" => "0"; +requires "Crypt::AuthEnc::GCM" => "0"; requires "Crypt::Mode::CBC" => "0"; requires "Crypt::OpenSSL::Bignum" => "0"; requires "Crypt::OpenSSL::RSA" => "0"; diff --git a/dist.ini b/dist.ini index cfd2572..8d03553 100644 --- a/dist.ini +++ b/dist.ini @@ -20,6 +20,7 @@ MIME::Base64 = 0 XML::LibXML = 0 Crypt::Mode::CBC = 0 Crypt::Random = 0 +Crypt::AuthEnc::GCM = 0 [Prereqs / TestRequires] Test::More = 0 diff --git a/lib/XML/Enc.pm b/lib/XML/Enc.pm index 94fa358..72eaeb9 100644 --- a/lib/XML/Enc.pm +++ b/lib/XML/Enc.pm @@ -9,12 +9,13 @@ use Carp; use XML::LibXML; use Crypt::OpenSSL::RSA; use Crypt::Mode::CBC; +use Crypt::AuthEnc::GCM; use MIME::Base64 qw/decode_base64 encode_base64/; use Crypt::Random qw( makerandom_octet ); use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS $DEBUG); -our $VERSION = '0.03'; +our $VERSION = '0.04'; our $DEBUG = 0; @@ -52,10 +53,37 @@ XML::Enc - XML Encryption # 5.2.2 AES - 128 bit initialization vector (IV) (16 bytes) my %encmethods = ( - 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => { ivsize => 8, keysize => 24, modename => 'DES_EDE' }, - 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => { ivsize => '16', keysize => 16, modename => 'AES' }, - 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => { ivsize => '16', keysize => 24, modename => 'AES' }, - 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => { ivsize => '16', keysize => 32, modename => 'AES' }, + 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => { + ivsize => 8, + keysize => 24, + modename => 'DES_EDE' }, + 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => { + ivsize => '16', + keysize => 16, + modename => 'AES' }, + 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => { + ivsize => '16', + keysize => 24, + modename => 'AES' }, + 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => { + ivsize => '16', + keysize => 32, + modename => 'AES' }, + 'http://www.w3.org/2009/xmlenc11#aes128-gcm' => { + ivsize => '12', + keysize => 16, + modename => 'AES', + tagsize => 16 }, + 'http://www.w3.org/2009/xmlenc11#aes192-gcm' => { + ivsize => '12', + keysize => 24, + modename => 'AES', + tagsize => 16 }, + 'http://www.w3.org/2009/xmlenc11#aes256-gcm' => { + ivsize => '12', + keysize => 32, + modename => 'AES', + tagsize => 16 }, ); =head2 new( ... ) @@ -293,6 +321,9 @@ sub _setEncryptionMethod { 'aes192-cbc' => 'http://www.w3.org/2001/04/xmlenc#aes192-cbc', 'aes256-cbc' => 'http://www.w3.org/2001/04/xmlenc#aes256-cbc', 'tripledes-cbc' => 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc', + 'aes128-gcm' => 'http://www.w3.org/2009/xmlenc11#aes128-gcm', + 'aes192-gcm' => 'http://www.w3.org/2009/xmlenc11#aes192-gcm', + 'aes256-gcm' => 'http://www.w3.org/2009/xmlenc11#aes256-gcm', ); return exists($methods{$method}) ? $methods{$method} : $methods{'aes256-cbc'}; @@ -327,7 +358,7 @@ sub _setKeyEncryptionMethod { 'rsa-oaep-mgf1p' => 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p', ); - return exists($methods{$method}) ? $methods{$method} : $methods{'rsa-1_5'}; + return exists($methods{$method}) ? $methods{$method} : $methods{'rsa-oaep-mgf1p'}; } sub _DecryptData { @@ -338,23 +369,35 @@ sub _DecryptData { my $iv; my $encrypted; - my $cbc; + my $plaintext; + + my $ivsize = $encmethods{$method}->{ivsize}; + my $tagsize = $encmethods{$method}->{tagsize}; + + $iv = substr $encrypteddata, 0, $ivsize; + $encrypted = substr $encrypteddata, $ivsize; # XML Encryption 5.2 Block Encryption Algorithms # The resulting cipher text is prefixed by the IV. - if (defined $encmethods{$method}){ - my $blksize = $encmethods{$method}->{ivsize}; - #print "Block Size: ", $blksize; - $iv = substr $encrypteddata, 0, $blksize; - $encrypted = substr $encrypteddata, $blksize; - $cbc = Crypt::Mode::CBC->new($encmethods{$method}->{modename}, 0); + if (defined $encmethods{$method} & $method !~ /gcm/ ){ + my $cbc = Crypt::Mode::CBC->new($encmethods{$method}->{modename}, 0); + $plaintext = $self->_remove_padding($cbc->decrypt($encrypted, $key, $iv)); + } elsif (defined $encmethods{$method} & $method =~ /gcm/ ){ + my $gcm = Crypt::AuthEnc::GCM->new("AES", $key, $iv); + + # Note that GCM support for additional authentication + # data is not used in the XML specification. + my $tag = substr $encrypted, - $tagsize; + $encrypted = substr $encrypted, 0, (length $encrypted) - $tagsize; + $plaintext = $gcm->decrypt_add($encrypted); + if ( ! $gcm->decrypt_done($tag) ) { + die "Tag expected did not match returned Tag"; + } } else { die "Unsupported Encryption Algorithm"; } - my $plaintext = $cbc->decrypt($encrypted, $key, $iv); - - return $self->_remove_padding($plaintext); + return $plaintext; } sub _EncryptData { @@ -363,25 +406,33 @@ sub _EncryptData { my $data = shift; my $key = shift; - my $iv; - my $cbc; + my $cipherdata; + my $ivsize = $encmethods{$method}->{ivsize}; + my $keysize = $encmethods{$method}->{keysize}; - # XML Encryption 5.2 Block Encryption Algorithms - # The resulting cipher text is prefixed by the IV. - if (defined $encmethods{$method}){ - my $blksize = $encmethods{$method}->{ivsize}; - my $keysize = $encmethods{$method}->{keysize}; - $iv = makerandom_octet ( Length => $blksize); - ${$key} = makerandom_octet ( Length => $keysize); - $data = $self->_add_padding($data, $blksize); - $cbc = Crypt::Mode::CBC->new($encmethods{$method}->{modename}, 0); + my $iv = makerandom_octet ( Length => $ivsize); + ${$key} = makerandom_octet ( Length => $keysize); + + if (defined $encmethods{$method} & $method !~ /gcm/ ){ + my $cbc = Crypt::Mode::CBC->new($encmethods{$method}->{modename}, 0); + # XML Encryption 5.2 Block Encryption Algorithms + # The resulting cipher text is prefixed by the IV. + $data = $self->_add_padding($data, $ivsize); + $cipherdata = $iv . $cbc->encrypt($data, ${$key}, $iv); + } elsif (defined $encmethods{$method} & $method =~ /gcm/ ){ + my $gcm = Crypt::AuthEnc::GCM->new($encmethods{$method}->{modename}, ${$key}, $iv); + + # Note that GCM support for additional authentication + # data is not used in the XML specification. + my $encrypted = $gcm->encrypt_add($data); + my $tag = $gcm->encrypt_done(); + + $cipherdata = $iv . $encrypted . $tag; } else { die "Unsupported Encryption Algorithm"; } - my $encrypted = $iv . $cbc->encrypt($data, ${$key}, $iv); - - return $encrypted; + return $cipherdata; } sub _DecryptKey { diff --git a/t/06-test-encryption-methods.t b/t/06-test-encryption-methods.t index 6e4c1bb..c56bde2 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 => 32; +use Test::More tests => 56; use XML::Enc; use MIME::Base64 qw/decode_base64 encode_base64/; use File::Which; @@ -13,7 +13,7 @@ my $xml = <<'XML'; XML my @key_methods = qw/rsa-1_5 rsa-oaep-mgf1p/; -my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc/; +my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc aes128-gcm aes192-gcm aes256-gcm/; foreach my $km (@key_methods) { foreach my $dm (@data_methods) { @@ -34,6 +34,11 @@ foreach my $km (@key_methods) { SKIP: { skip "xmlsec1 not installed", 2 unless which('xmlsec1'); + my $version; + if (`xmlsec1 version` =~ m/(\d+\.\d+\.\d+)/) { + $version = $1; + }; + skip "xmlsec version 1.2.27 minimum for GCM", 2 if $version lt '1.2.27'; ok( open XML, '>', 'tmp.xml' ); print XML $encrypted; close XML; diff --git a/t/07-decrypt-xmlsec.t b/t/07-decrypt-xmlsec.t index a4ec989..f7ce229 100644 --- a/t/07-decrypt-xmlsec.t +++ b/t/07-decrypt-xmlsec.t @@ -1,6 +1,6 @@ use strict; use warnings; -use Test::More tests => 40; +use Test::More tests => 70; use XML::Enc; use MIME::Base64 qw/decode_base64/; use File::Which; @@ -19,13 +19,26 @@ my $plaintext = <<'UNENCRYPTED'; UNENCRYPTED my @key_methods = qw/rsa-1_5 rsa-oaep-mgf1p/; -my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc/; +my @data_methods = qw/aes128-cbc aes192-cbc aes256-cbc tripledes-cbc aes128-gcm aes192-gcm aes256-gcm/; + +my %uri = ( + 'aes128-cbc' => 'http://www.w3.org/2001/04/xmlenc#', + 'aes192-cbc' => 'http://www.w3.org/2001/04/xmlenc#', + 'aes256-cbc' => 'http://www.w3.org/2001/04/xmlenc#', + 'tripledes-cbc' => 'http://www.w3.org/2001/04/xmlenc#', + 'aes128-gcm' => 'http://www.w3.org/2009/xmlenc11#', + 'aes192-gcm' => 'http://www.w3.org/2009/xmlenc11#', + 'aes256-gcm' => 'http://www.w3.org/2009/xmlenc11#', + ); my %sesskey = ( 'aes128-cbc' => 'aes-128', 'aes192-cbc' => 'aes-192', 'aes256-cbc' => 'aes-256', 'tripledes-cbc' => 'des-192', + 'aes128-gcm' => 'aes-128-GCM', + 'aes192-gcm' => 'aes-192-GCM', + 'aes256-gcm' => 'aes-256-GCM', ); foreach my $km (@key_methods) { @@ -41,7 +54,7 @@ XML Security Library example: Original XML xmlns="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element"> + "$uri{$dm}$dm"/> + "$uri{$dm}$dm"/> ', 'plaintext.xml' ); print XML $plaintext;