File Coverage

blib/lib/Net/SAML2/Binding/Redirect.pm
Criterion Covered Total %
statement 69 82 84.1
branch 9 24 37.5
condition 1 3 33.3
subroutine 14 14 100.0
pod 2 2 100.0
total 95 125 76.0


line stmt bran cond sub pod time code
1             package Net::SAML2::Binding::Redirect;
2              
3 13     13   136 use strict;
  13         30  
  13         486  
4 13     13   78 use warnings;
  13         28  
  13         421  
5              
6 13     13   68 use Moose;
  13         26  
  13         116  
7 13     13   95556 use MooseX::Types::URI qw/ Uri /;
  13         34  
  13         174  
8              
9             our $VERSION = '0.41';
10              
11             # ABSTRACT: Net::SAML2::Binding::Redirect - HTTP Redirect binding for SAML
12              
13              
14 13     13   27758 use MIME::Base64 qw/ encode_base64 decode_base64 /;
  13         36  
  13         1070  
15 13     13   19616 use IO::Compress::RawDeflate qw/ rawdeflate /;
  13         412727  
  13         1258  
16 13     13   8557 use IO::Uncompress::RawInflate qw/ rawinflate /;
  13         173942  
  13         1010  
17 13     13   129 use URI;
  13         36  
  13         326  
18 13     13   80 use URI::QueryParam;
  13         29  
  13         276  
19 13     13   7169 use Crypt::OpenSSL::RSA;
  13         76635  
  13         709  
20 13     13   116 use Crypt::OpenSSL::X509;
  13         39  
  13         654  
21 13     13   8685 use File::Slurp qw/ read_file /;
  13         234871  
  13         9272  
22              
23              
24             has 'key' => (isa => 'Str', is => 'ro', required => 1);
25             has 'cert' => (isa => 'Str', is => 'ro', required => 1);
26             has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1);
27             has 'param' => (isa => 'Str', is => 'ro', required => 1);
28             has 'sig_hash' => (isa => 'Str', is => 'ro', required => 0);
29              
30              
31             sub sign {
32 2     2 1 660 my ($self, $request, $relaystate) = @_;
33              
34 2         9 my $input = "$request";
35 2         4 my $output = '';
36              
37 2         17 rawdeflate \$input => \$output;
38 2         4480 my $req = encode_base64($output, '');
39              
40 2         126 my $u = URI->new($self->url);
41 2         247 $u->query_param($self->param, $req);
42 2 100       596 $u->query_param('RelayState', $relaystate) if defined $relaystate;
43              
44 2         534 my $key_string = read_file($self->key);
45 2         1071 my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string);
46              
47 2 50 33     36 if ( exists $self->{ sig_hash } && grep { $_ eq $self->{ sig_hash } } ('sha224', 'sha256', 'sha384', 'sha512'))
  0         0  
48             {
49 0 0       0 if ($self->{ sig_hash } eq 'sha224') {
    0          
    0          
    0          
50 0         0 $rsa_priv->use_sha224_hash;
51             } elsif ($self->{ sig_hash } eq 'sha256') {
52 0         0 $rsa_priv->use_sha256_hash;
53             } elsif ($self->{ sig_hash } eq 'sha384') {
54 0         0 $rsa_priv->use_sha384_hash;
55             } elsif ($self->{ sig_hash } eq 'sha512') {
56 0         0 $rsa_priv->use_sha512_hash;
57             } else {
58 0         0 die "Unsupported Signing Hash";
59             }
60 0         0 $u->query_param('SigAlg', 'http://www.w3.org/2001/04/xmldsig-more#rsa-' . $self->{ sig_hash });
61             }
62             else { #$self->{ sig_hash } eq 'sha1' or something unsupported
63 2         13 $rsa_priv->use_sha1_hash;
64 2         10 $u->query_param('SigAlg', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1');
65             }
66              
67 2         867 my $to_sign = $u->query;
68 2         3457 my $sig = encode_base64($rsa_priv->sign($to_sign), '');
69 2         17 $u->query_param('Signature', $sig);
70              
71 2         1208 my $url = $u->as_string;
72 2         45 return $url;
73             }
74              
75              
76             sub verify {
77 2     2 1 837 my ($self, $url) = @_;
78 2         11 my $u = URI->new($url);
79              
80             # verify the response
81 2         175 my $sigalg = $u->query_param('SigAlg');
82              
83 2         540 my $cert = Crypt::OpenSSL::X509->new_from_string($self->cert);
84 2         91 my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
85              
86 2 50       1295 if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
    50          
    50          
    50          
    50          
87 0         0 $rsa_pub->use_sha256_hash;
88             } elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224') {
89 0         0 $rsa_pub->use_sha224_hash;
90             } elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384') {
91 0         0 $rsa_pub->use_sha384_hash;
92             } elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512') {
93 0         0 $rsa_pub->use_sha512_hash;
94             } elsif ($sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
95 2         11 $rsa_pub->use_sha1_hash;
96             } else {
97 0         0 die "Unsupported Signature Algorithim: $sigalg";
98             }
99              
100 2         27 my $sig = decode_base64($u->query_param_delete('Signature'));
101 2         1052 my $signed = $u->query;
102 2 50       186 die "bad sig" unless $rsa_pub->verify($signed, $sig);
103              
104             # unpack the SAML request
105 2         78 my $deflated = decode_base64($u->query_param($self->param));
106 2         298 my $request = '';
107 2         16 rawinflate \$deflated => \$request;
108              
109             # unpack the relaystate
110 2         4076 my $relaystate = $u->query_param('RelayState');
111              
112 2         355 return ($request, $relaystate);
113             }
114              
115             __PACKAGE__->meta->make_immutable;
116              
117             __END__
118              
119             =pod
120              
121             =encoding UTF-8
122              
123             =head1 NAME
124              
125             Net::SAML2::Binding::Redirect - Net::SAML2::Binding::Redirect - HTTP Redirect binding for SAML
126              
127             =head1 VERSION
128              
129             version 0.41
130              
131             =head1 SYNOPSIS
132              
133             my $redirect = Net::SAML2::Binding::Redirect->new(
134             key => '/path/to/SPsign-nopw-key.pem', # Service Provider (SP) private key
135             url => $sso_url, # Service Provider Single Sign Out URL
136             param => 'SAMLRequest' OR 'SAMLResponse', # Type of request
137             cert => $idp->cert('signing') # Identity Provider (IdP) certificate
138             sig_hash => 'sha1', 'sha224', 'sha256', 'sha384', 'sha512' # Signature to sign request
139             );
140              
141             my $url = $redirect->sign($authnreq);
142              
143             my $ret = $redirect->verify($url);
144              
145             =head1 NAME
146              
147             Net::SAML2::Binding::Redirect
148              
149             =head1 METHODS
150              
151             =head2 new( ... )
152              
153             Constructor. Creates an instance of the Redirect binding.
154              
155             Arguments:
156              
157             =over
158              
159             =item B<key>
160              
161             The SP's (Service Provider) also known as your application's signing key
162             that your application uses to sign the AuthnRequest. Some IdPs may not
163             verify the signature.
164              
165             =item B<cert>
166              
167             IdP's (Identity Provider's) certificate that is used to verify a signed
168             Redirect from the IdP. It is used to verify the signature of the Redirect
169             response.
170              
171             =item B<url>
172              
173             IdP's SSO (Single Sign Out) service url for the Redirect binding
174              
175             =item B<param>
176              
177             query param name to use (SAMLRequest, SAMLResponse)
178              
179             =item B<sig_hash>
180              
181             RSA hash to use to sign request
182              
183             Supported:
184              
185             sha1, sha224, sha256, sha384, sha512
186              
187             sha1 is current default but will change by version 44
188              
189             =back
190              
191             =head2 sign( $request, $relaystate )
192              
193             Signs the given request, and returns the URL to which the user's
194             browser should be redirected.
195              
196             Accepts an optional RelayState parameter, a string which will be
197             returned to the requestor when the user returns from the
198             authentication process with the IdP.
199              
200             =head2 verify( $url )
201              
202             Decode a Redirect binding URL.
203              
204             Verifies the signature on the response.
205              
206             =head1 AUTHOR
207              
208             Chris Andrews <chrisa@cpan.org>
209              
210             =head1 COPYRIGHT AND LICENSE
211              
212             This software is copyright (c) 2021 by Chris Andrews and Others, see the git log.
213              
214             This is free software; you can redistribute it and/or modify it under
215             the same terms as the Perl 5 programming language system itself.
216              
217             =cut