Skip to content

Commit

Permalink
Add support to SP for SSO redirect binding without signed auth request
Browse files Browse the repository at this point in the history
Signed-off-by: Wesley Schwengle <waterkip@cpan.org>
  • Loading branch information
waterkip committed Sep 27, 2022
1 parent 736bf78 commit 1a5796b
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 49 deletions.
110 changes: 73 additions & 37 deletions lib/Net/SAML2/Binding/Redirect.pm
Original file line number Diff line number Diff line change
Expand Up @@ -154,78 +154,84 @@ around BUILDARGS => sub {
return $self->$orig(%params);
};

=head2 sign( $request, $relaystate )
=head2 get_redirect_uri($authn_request, $relaystate)
Signs the given request, and returns the URL to which the user's
browser should be redirected.
Get the redirect URI for a given request, and returns the URL to which the
user's browser should be redirected.
Accepts an optional RelayState parameter, a string which will be
returned to the requestor when the user returns from the
authentication process with the IdP.
The request is signed unless the the object has been instantiated with
C<<insecure => 1>>.
=cut

sub sign {
my ($self, $request, $relaystate) = @_;
sub get_redirect_uri {
my $self = shift;
my $request = shift;

my $input = "$request";
if (!defined $request) {
croak("Unable to create redirect URI without a request");
}

my $relaystate = shift;

my $input = "$request";
my $output = '';

rawdeflate \$input => \$output;
my $req = encode_base64($output, '');

my $u = URI->new($self->url);
$u->query_param($self->param, $req);
$u->query_param('RelayState', $relaystate) if defined $relaystate;
my $uri = URI->new($self->url);
$uri->query_param($self->param, $req);
$uri->query_param('RelayState', $relaystate) if defined $relaystate;

return $u->as_string if $self->insecure;
return $uri->as_string if $self->insecure;
return $self->_sign_redirect_uri($uri);
}

sub _sign_redirect_uri {
my $self = shift;
my $uri = shift;

my $key_string = read_text($self->key);
my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string);

my $method = "use_" . $self->sig_hash . "_hash";
$rsa_priv->$method;

$u->query_param('SigAlg',
$uri->query_param('SigAlg',
$self->sig_hash eq 'sha1'
? 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
: 'http://www.w3.org/2001/04/xmldsig-more#rsa-' . $self->sig_hash);

my $to_sign = $u->query;
my $to_sign = $uri->query;
my $sig = encode_base64($rsa_priv->sign($to_sign), '');
$u->query_param('Signature', $sig);

return $u->as_string;
$uri->query_param('Signature', $sig);
return $uri->as_string;
}

sub _verify {
my ($self, $sigalg, $signed, $sig) = @_;
=head2 sign( $request, $relaystate )
foreach my $crt (@{$self->cert}) {
my $cert = Crypt::OpenSSL::X509->new_from_string($crt);
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
Signs the given request, and returns the URL to which the user's
browser should be redirected.
if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
$rsa_pub->use_sha256_hash;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224') {
$rsa_pub->use_sha224_hash;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384') {
$rsa_pub->use_sha384_hash;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512') {
$rsa_pub->use_sha512_hash;
} elsif ($sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
$rsa_pub->use_sha1_hash;
}
else {
warn "Unsupported Signature Algorithim: $sigalg, defaulting to sha256" if $self->debug;
}
Accepts an optional RelayState parameter, a string which will be
returned to the requestor when the user returns from the
authentication process with the IdP.
return 1 if $rsa_pub->verify($signed, $sig);
=cut

warn "Unable to verify with " . $cert->subject if $self->debug;
sub sign {
my $self = shift;

if ($self->insecure) {
croak("Cannot sign an insecure request!");
}

croak("Unable to verify the XML signature");
return $self->get_redirect_uri(@_);
}

=head2 verify( $query_string )
Expand Down Expand Up @@ -272,4 +278,34 @@ sub verify {
return ($request, $relaystate);
}

sub _verify {
my ($self, $sigalg, $signed, $sig) = @_;

foreach my $crt (@{$self->cert}) {
my $cert = Crypt::OpenSSL::X509->new_from_string($crt);
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;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224') {
$rsa_pub->use_sha224_hash;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384') {
$rsa_pub->use_sha384_hash;
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512') {
$rsa_pub->use_sha512_hash;
} elsif ($sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
$rsa_pub->use_sha1_hash;
}
else {
warn "Unsupported Signature Algorithim: $sigalg, defaulting to sha256" if $self->debug;
}

return 1 if $rsa_pub->verify($signed, $sig);

warn "Unable to verify with " . $cert->subject if $self->debug;
}

croak("Unable to verify the XML signature");
}

__PACKAGE__->meta->make_immutable;
12 changes: 11 additions & 1 deletion lib/Net/SAML2/SP.pm
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,20 @@ parameter involved - typically C<SAMLRequest>.
sub sso_redirect_binding {
my ($self, $idp, $param) = @_;

unless ($idp) {
croak("Unable to create a redirect binding without an IDP");
}

$param = 'SAMLRequest' unless $param;

my $redirect = Net::SAML2::Binding::Redirect->new(
url => $idp->sso_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
cert => $idp->cert('signing'),
key => $self->key,
$self->authnreq_signed ? (
key => $self->key,
) : (
insecure => 1,
),
param => $param,
);

Expand Down
50 changes: 39 additions & 11 deletions t/06-redirect-binding.t
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use strict;
use warnings;
use Test::Lib;
use Test::Net::SAML2;
use URI;

use Net::SAML2::IdP;
use Net::SAML2::Binding::Redirect;
Expand Down Expand Up @@ -44,6 +45,11 @@ like(
"location checks out"
);

my $uri = URI->new($location);
is($uri->host, 'sso.dev.venda.com', "Correct hostname on the location");
my %query = $uri->query_form;
cmp_deeply([sort qw(SAMLRequest RelayState SigAlg Signature)], [sort keys %query], "Signed redirect URI");

my ($request, $relaystate) = $redirect->verify($location);

test_xml_attribute_ok($xp, '/saml2p:AuthnRequest/@ID', qr/^NETSAML2_/,
Expand Down Expand Up @@ -95,16 +101,38 @@ throws_ok(
"Need a key for SAMLRequest"
);

lives_ok(
sub {
Net::SAML2::Binding::Redirect->new(
url => 'https://foo.example.com',
insecure => 1,
);
},
"We don't need a key for an insecure SAMLRequest"
);


{
my $binding;
lives_ok(
sub {
$binding = Net::SAML2::Binding::Redirect->new(
url => 'https://foo.example.com',
insecure => 1,
);
},
"We don't need a key for an insecure SAMLRequest"
);

my $uri = $binding->get_redirect_uri($authnreq, 'https://foo.bar.example.com') ;
$uri = URI->new($uri);
my %query = $uri->query_form;
cmp_deeply([sort qw(SAMLRequest RelayState)], [sort keys %query], "Unsigned redirect URI");

my $sp = net_saml2_sp(authnreq_signed => 0);
$binding = $sp->sso_redirect_binding($idp, 'SAMLRequest');

throws_ok(
sub {
$binding->sign($authnreq, 'https://foo.bar.example.com') ;
},
qr#Cannot sign an insecure request#,
"Unable to sign insecure requests"
);

$uri = $binding->get_redirect_uri($authnreq, 'https://foo.bar.example.com') ;
$uri = URI->new($uri);
%query = $uri->query_form;
cmp_deeply([sort qw(SAMLRequest RelayState)], [sort keys %query], "Unsigned redirect URI via SP");
}

done_testing;

0 comments on commit 1a5796b

Please sign in to comment.