diff --git a/lib/Net/SAML2/Binding/POST.pm b/lib/Net/SAML2/Binding/POST.pm index 78d04e7..5d65e38 100644 --- a/lib/Net/SAML2/Binding/POST.pm +++ b/lib/Net/SAML2/Binding/POST.pm @@ -43,7 +43,7 @@ sub handle_response { # unpack and check the signature my $xml = decode_base64($response); - my $x = Net::SAML2::XML::Sig->new({ x509 => 1 }); + my $x = Net::SAML2::XML::Sig->new({ x509 => 1, cert => $self->cacert }); my $ret = $x->verify($xml); die "signature check failed" unless $ret; diff --git a/lib/Net/SAML2/Binding/Redirect.pm b/lib/Net/SAML2/Binding/Redirect.pm index 42d7c9b..5c0391c 100644 --- a/lib/Net/SAML2/Binding/Redirect.pm +++ b/lib/Net/SAML2/Binding/Redirect.pm @@ -106,13 +106,16 @@ sub verify { my $u = URI->new($url); # verify the response - my $sigalg = $u->query_param('SigAlg'); - die "can't verify '$sigalg' signatures" - unless $sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - my $cert = Crypt::OpenSSL::X509->new_from_string($self->cert); my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey); - + + my $sigalg = $u->query_param('SigAlg'); + if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') { + $rsa_pub->use_sha256_hash(); + } else { + die "can't verify '$sigalg' signatures" + unless $sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + } my $sig = decode_base64($u->query_param_delete('Signature')); my $signed = $u->query; die "bad sig" unless $rsa_pub->verify($signed, $sig); diff --git a/lib/Net/SAML2/IdP.pm b/lib/Net/SAML2/IdP.pm index 627cdf1..ecc8fd5 100644 --- a/lib/Net/SAML2/IdP.pm +++ b/lib/Net/SAML2/IdP.pm @@ -33,8 +33,8 @@ Constructor has 'entityid' => (isa => Str, is => 'ro', required => 1); has 'cacert' => (isa => Str, is => 'ro', required => 1); has 'sso_urls' => (isa => HashRef[Str], is => 'ro', required => 1); -has 'slo_urls' => (isa => HashRef[Str], is => 'ro', required => 1); -has 'art_urls' => (isa => HashRef[Str], is => 'ro', required => 1); +has 'slo_urls' => (isa => HashRef[Str], is => 'ro'); +has 'art_urls' => (isa => HashRef[Str], is => 'ro'); has 'certs' => (isa => HashRef[Str], is => 'ro', required => 1); has 'formats' => (isa => HashRef[Str], is => 'ro', required => 1); has 'default_format' => (isa => Str, is => 'ro', required => 1); @@ -123,8 +123,8 @@ sub new_from_xml { my $self = $class->new( entityid => $xpath->findvalue('//md:EntityDescriptor/@entityID')->value, sso_urls => $data->{SSO}, - slo_urls => $data->{SLO}, - art_urls => $data->{Art}, + (defined $data->{SLO} ? (slo_urls => $data->{SLO}) : () ), + (defined $data->{Art} ? (art_urls => $data->{Art}) : () ), certs => $data->{Cert}, formats => $data->{NameIDFormat}, default_format => $data->{DefaultFormat}, diff --git a/lib/Net/SAML2/Role/ProtocolMessage.pm b/lib/Net/SAML2/Role/ProtocolMessage.pm index d34051f..418ab56 100644 --- a/lib/Net/SAML2/Role/ProtocolMessage.pm +++ b/lib/Net/SAML2/Role/ProtocolMessage.pm @@ -27,7 +27,7 @@ around 'BUILDARGS' => sub { my %args = @_; # random ID for this message - $args{id} ||= unpack 'H*', Crypt::OpenSSL::Random::random_pseudo_bytes(16); + $args{id} ||='_' . unpack 'H*', Crypt::OpenSSL::Random::random_pseudo_bytes(16); # IssueInstant in UTC my $dt = DateTime->now( time_zone => 'UTC' ); diff --git a/lib/Net/SAML2/XML/Sig.pm b/lib/Net/SAML2/XML/Sig.pm index 265f499..7410d3b 100644 --- a/lib/Net/SAML2/XML/Sig.pm +++ b/lib/Net/SAML2/XML/Sig.pm @@ -17,7 +17,7 @@ use base qw/Exporter/; use strict; -use Digest::SHA1 qw(sha1 sha1_base64); +use Digest::SHA qw(sha1 sha256); use XML::XPath; use MIME::Base64; use Carp; @@ -136,9 +136,24 @@ sub verify { my $signed_info = XML::XPath::XMLParser::as_string($signed_info_node); my $signed_info_canon = $self->_canonicalize_xml( $signed_info ); + my $sigalg = $self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:SignatureMethod/@Algorithm'); + my $digest_method = $self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:Reference/dsig:DigestMethod/@Algorithm'); + my $digest = _trim($self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:Reference/dsig:DigestValue')); + + my $signed_xml = $self->_get_signed_xml(); + my $canonical = $self->_transform($signed_xml, $signature_node); + my $digest_bin; + if ($digest_method eq 'http://www.w3.org/2000/09/xmldsig#sha1') { + $digest_bin = sha1($canonical); + } elsif ($digest_method eq 'http://www.w3.org/2001/04/xmlenc#sha256') { + $digest_bin = sha256($canonical); + } + + return 0 unless ($digest eq _trim(encode_base64($digest_bin))); + if (defined $self->{cert_obj}) { # use the provided cert to verify - return 0 unless $self->_verify_x509_cert($self->{cert_obj},$signed_info_canon,$signature); + return 0 unless $self->_verify_x509_cert($self->{cert_obj},$signed_info_canon,$signature,$sigalg); } else { # extract the certficate or key from the document @@ -157,15 +172,7 @@ sub verify { } } - my $digest_method = $self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:Reference/dsig:DigestMethod/@Algorithm'); - my $digest = _trim($self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:Reference/dsig:DigestValue')); - - my $signed_xml = $self->_get_signed_xml(); - my $canonical = $self->_transform($signed_xml, $signature_node); - my $digest_bin = sha1($canonical); - - return 1 if ($digest eq _trim(encode_base64($digest_bin))); - return 0; + return 1; } sub signer_cert { @@ -288,13 +295,17 @@ sub _verify_x509 { sub _verify_x509_cert { my $self = shift; - my ($cert, $canonical, $sig) = @_; + my ($cert, $canonical, $sig, $sigalg) = @_; eval { require Crypt::OpenSSL::RSA; }; my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey); + if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') { + $rsa_pub->use_sha256_hash(); + } + # Decode signature and verify my $bin_signature = decode_base64($sig);