1- use strict;
2- use warnings;
31package Net::SAML2::Binding::Redirect ;
2+ use Moose;
3+
44# VERSION
55
6- use Moose;
6+ use Carp qw( croak) ;
7+ use Crypt::OpenSSL::RSA;
8+ use Crypt::OpenSSL::X509;
9+ use File::Slurper qw/ read_text / ;
10+ use IO::Compress::RawDeflate qw/ rawdeflate / ;
11+ use IO::Uncompress::RawInflate qw/ rawinflate / ;
12+ use MIME::Base64 qw/ encode_base64 decode_base64 / ;
713use MooseX::Types::URI qw/ Uri / ;
814use Net::SAML2::Types qw( signingAlgorithm SAMLRequestType) ;
9- use Carp qw( croak) ;
15+ use URI::Encode qw/ uri_decode/ ;
16+ use URI::Escape qw( uri_unescape) ;
17+ use URI::QueryParam;
18+ use URI;
1019
1120# ABSTRACT: Net::SAML2::Binding::Redirect - HTTP Redirect binding for SAML
1221
13- =head1 NAME
14-
15- Net::SAML2::Binding::Redirect
16-
1722=head1 SYNOPSIS
1823
1924 my $redirect = Net::SAML2::Binding::Redirect->new(
@@ -32,16 +37,6 @@ Net::SAML2::Binding::Redirect
3237
3338=cut
3439
35- use MIME::Base64 qw/ encode_base64 decode_base64 / ;
36- use IO::Compress::RawDeflate qw/ rawdeflate / ;
37- use IO::Uncompress::RawInflate qw/ rawinflate / ;
38- use URI;
39- use URI::QueryParam;
40- use Crypt::OpenSSL::RSA;
41- use Crypt::OpenSSL::X509;
42- use File::Slurper qw/ read_text / ;
43- use URI::Encode qw/ uri_decode/ ;
44-
4540=head2 new( ... )
4641
4742Constructor. Creates an instance of the Redirect binding.
@@ -220,7 +215,7 @@ sub sign {
220215 return $u -> as_string;
221216}
222217
223- sub _verified {
218+ sub _verify {
224219 my ($self , $sigalg , $signed , $sig ) = @_ ;
225220
226221 foreach my $crt (@{$self -> cert}) {
@@ -237,84 +232,60 @@ sub _verified {
237232 $rsa_pub -> use_sha512_hash;
238233 } elsif ($sigalg eq ' http://www.w3.org/2000/09/xmldsig#rsa-sha1' ) {
239234 $rsa_pub -> use_sha1_hash;
240- } else {
241- warn " Unsupported Signature Algorithim: $sigalg " if ($self -> debug);
242235 }
243-
244- if ($rsa_pub -> verify($signed , $sig )) {
245- return 1;
236+ else {
237+ warn " Unsupported Signature Algorithim: $sigalg , defaulting to sha256" if $self -> debug;
246238 }
247239
248- warn " Unable to verify with " . $cert -> subject if ($self -> debug);
240+ return 1 if $rsa_pub -> verify($signed , $sig );
241+
242+ warn " Unable to verify with " . $cert -> subject if $self -> debug;
249243 }
250244
251- die " bad sig " ;
245+ croak( " Unable to verify the XML signature " ) ;
252246}
253247
254- =head2 verify( $url )
248+ =head2 verify( $query_string )
255249
256250Decode a Redirect binding URL.
257251
258252Verifies the signature on the response.
259253
254+ Requires the *raw* query string to be passed, because L<URI> parses and
255+ re-encodes URI-escapes in uppercase (C<%3f > becomes C<%3F > , for instance),
256+ which leads to signature verification failures if the other party uses lower
257+ case (or mixed case).
258+
260259=cut
261260
262261sub verify {
263262 my ($self , $url ) = @_ ;
264- my $u = URI-> new($url );
265263
266- # verify the response
267- my $sigalg = $u -> query_param( ' SigAlg ' ) ;
264+ # This now becomes the query string
265+ $url =~ s # ^https?://.+ \? ## ;
268266
269- my $signed ;
270- my $saml_request ;
271- my $sig = $u -> query_param_delete(' Signature' );
267+ my %params = map { split (/ =/ , $_ , 2) } split (/ &/ , $url );
272268
273- # During the verify the only query parameters that should be in the query are
274- # 'SAMLRequest', 'RelayState', 'Sig', 'SigAlg' the other parameter values are
275- # deleted from the URI query that was created from the URL that was passed
276- # to the verify function
277- my @signed_params = (' SAMLRequest' , ' SAMLResponse' , ' RelayState' , ' Sig' , ' SigAlg' );
278-
279- for my $key ($u -> query_param) {
280- if (grep /$key /, @signed_params ) {
281- next ;
282- }
283- $u -> query_param_delete($key );
284- }
269+ my $sigalg = uri_unescape($params {SigAlg });
285270
286- # Some IdPs (PingIdentity) seem to double encode the LogoutResponse URL
287- if ($self -> sls_double_encoded_response) {
288- # if ($sigalg =~ m/%/) {
289- $signed = uri_decode($u -> query);
290- $sig = uri_decode($sig );
291- $sigalg = uri_decode($sigalg );
292- $saml_request = uri_decode($u -> query_param($self -> param));
293- } else {
294- $signed = $u -> query;
295- $saml_request = $u -> query_param($self -> param);
296- }
271+ my $encoded_sig = uri_unescape($params {Signature });
272+ my $sig = decode_base64($encoded_sig );
297273
298- # What can we say about this one Microsoft Azure uses lower case in the
299- # URL encoding %2f not %2F. As it is signed as %2f the resulting signed
300- # needs to change it to lowercase if the application layer reencoded the URL.
301- if ($self -> sls_force_lcase_url_encoding) {
302- # TODO: This is a hack.
303- $signed =~ s / (%..)/ lc($1 )/ ge ;
274+ my @signed_parts ;
275+ for my $p ($self -> param, qw( RelayState SigAlg) ) {
276+ push @signed_parts , join (' =' , $p , $params {$p }) if exists $params {$p };
304277 }
278+ my $signed = join (' &' , @signed_parts );
305279
306- $sig = decode_base64($sig );
307-
308- $self -> _verified($sigalg , $signed , $sig );
280+ $self -> _verify($sigalg , $signed , $sig );
309281
310282 # unpack the SAML request
311- my $deflated = decode_base64($saml_request );
283+ my $deflated = decode_base64(uri_unescape( $params { $self -> param}) );
312284 my $request = ' ' ;
313285 rawinflate \$deflated => \$request ;
314286
315287 # unpack the relaystate
316- my $relaystate = $u -> query_param(' RelayState' );
317-
288+ my $relaystate = uri_unescape($params {' RelayState' });
318289 return ($request , $relaystate );
319290}
320291
0 commit comments