diff --git a/Makefile.PL b/Makefile.PL index d5262bf..f34ec4e 100755 --- a/Makefile.PL +++ b/Makefile.PL @@ -29,7 +29,7 @@ requires 'MooseX::Types::DateTime'; # XML::Sig's deps requires 'Class::Accessor'; -requires 'Digest::SHA1'; +requires 'Digest::SHA'; requires 'Crypt::OpenSSL::Bignum'; requires 'Crypt::OpenSSL::DSA'; requires 'XML::CanonicalizeXML'; diff --git a/lib/Net/SAML2/Binding/POST.pm b/lib/Net/SAML2/Binding/POST.pm index 78d04e7..003c09d 100644 --- a/lib/Net/SAML2/Binding/POST.pm +++ b/lib/Net/SAML2/Binding/POST.pm @@ -8,7 +8,9 @@ Net::SAML2::Binding::POST - HTTP POST binding for SAML2 =head1 SYNOPSIS - my $post = Net::SAML2::Binding::POST->new; + my $post = Net::SAML2::Binding::POST->new( + cacert => '/path/to/ca-cert.pem' + ); my $ret = $post->handle_response( $saml_response ); @@ -21,17 +23,25 @@ use Net::SAML2::XML::Sig; use MIME::Base64 qw/ decode_base64 /; use Crypt::OpenSSL::VerifyX509; -=head2 new() +=head2 new( ) Constructor. Returns an instance of the POST binding. -No arguments. +Arguments: + +=over + +=item B + +path to the CA certificate for verification + +=back =cut has 'cacert' => (isa => Str, is => 'ro', required => 1); -=head2 handle_response($response) +=head2 handle_response( $response ) Decodes and verifies the response provided, which should be the raw Base64-encoded response, from the SAMLResponse CGI parameter. diff --git a/lib/Net/SAML2/Binding/Redirect.pm b/lib/Net/SAML2/Binding/Redirect.pm index 42d7c9b..db38d31 100644 --- a/lib/Net/SAML2/Binding/Redirect.pm +++ b/lib/Net/SAML2/Binding/Redirect.pm @@ -13,6 +13,7 @@ Net::SAML2::Binding::Redirect key => 'sign-nopw-cert.pem', url => $sso_url, param => 'SAMLRequest', + cacert => '/path/to/cac-cert.pem' ); my $url = $redirect->sign($authnreq); @@ -45,10 +46,25 @@ Constructor. Creates an instance of the Redirect binding. Arguments: - * key - the signing key (for creating Redirect URLs) - * cert - the IdP's signing cert (for verifying Redirect URLs) - * url - the IdP's SSO service url for the Redirect binding - * param - the query param name to use (SAMLRequest, SAMLResponse) +=over + +=item B + +signing key (for creating Redirect URLs) + +=item B + +IdP's signing cert (for verifying Redirect URLs) + +=item B + +IdP's SSO service url for the Redirect binding + +=item B + +query param name to use (SAMLRequest, SAMLResponse) + +=back =cut @@ -57,7 +73,7 @@ has 'cert' => (isa => Str, is => 'ro', required => 1); has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'param' => (isa => Str, is => 'ro', required => 1); -=head2 sign($request, $relaystate) +=head2 sign( $request, $relaystate ) Signs the given request, and returns the URL to which the user's browser should be redirected. @@ -93,7 +109,7 @@ sub sign { return $url; } -=head2 verify($url) +=head2 verify( $url ) Decode a Redirect binding URL. diff --git a/lib/Net/SAML2/Binding/SOAP.pm b/lib/Net/SAML2/Binding/SOAP.pm index f188206..ab9f44e 100644 --- a/lib/Net/SAML2/Binding/SOAP.pm +++ b/lib/Net/SAML2/Binding/SOAP.pm @@ -34,12 +34,33 @@ the given IdP service url. Arguments: - * ua - (optionally) a LWP::UserAgent-compatible UA - * url - the service URL - * key - the key to sign with - * cert - the corresponding certificate - * idp_cert - the idp's signing certificate - * cacert - the CA for the SAML CoT +=over + +=item B + +(optional) a LWP::UserAgent-compatible UA + +=item B + +the service URL + +=item B + +the key to sign with + +=item B + +the corresponding certificate + +=item B + +the idp's signing certificate + +=item B + +the CA for the SAML CoT + +=back =cut @@ -52,7 +73,7 @@ has 'cert' => (isa => Str, is => 'ro', required => 1); has 'idp_cert' => (isa => Str, is => 'ro', required => 1); has 'cacert' => (isa => Str, is => 'ro', required => 1); -=head2 request($message) +=head2 request( $message ) Submit the message to the IdP's service. @@ -145,7 +166,7 @@ sub handle_request { return; } -=head2 create_soap_envelope($message) +=head2 create_soap_envelope( $message ) Signs and SOAP-wraps the given message. diff --git a/lib/Net/SAML2/IdP.pm b/lib/Net/SAML2/IdP.pm index 627cdf1..35bebaf 100644 --- a/lib/Net/SAML2/IdP.pm +++ b/lib/Net/SAML2/IdP.pm @@ -22,11 +22,15 @@ use HTTP::Request::Common; use LWP::UserAgent; use XML::XPath; -=head2 new +=head2 new( ) Constructor - * entityID +=over + +=item B + +=back =cut @@ -146,7 +150,7 @@ sub BUILD { } } -=head2 sso_url($binding) +=head2 sso_url( $binding ) Returns the url for the SSO service using the given binding. Binding name should be the full URI. @@ -158,7 +162,7 @@ sub sso_url { return $self->sso_urls->{$binding}; } -=head2 slo_url($binding) +=head2 slo_url( $binding ) Returns the url for the Single Logout Service using the given binding. Binding name should be the full URI. @@ -170,7 +174,7 @@ sub slo_url { return $self->slo_urls->{$binding}; } -=head2 art_url($binding) +=head2 art_url( $binding ) Returns the url for the Artifact Resolution service using the given binding. Binding name should be the full URI. @@ -182,9 +186,9 @@ sub art_url { return $self->art_urls->{$binding}; } -=head2 cert($use) +=head2 cert( $use ) -Returns the IdP's certificate for the given use (e.g. 'signing'). +Returns the IdP's certificate for the given use (e.g. C). =cut @@ -193,10 +197,10 @@ sub cert { return $self->certs->{$use}; } -=head2 binding($name) +=head2 binding( $name ) -Returns the full Binding URI for the given binding name. Includes this -module's currently-supported bindings. +Returns the full Binding URI for the given binding name (i.e. C or C). +Includes this module's currently-supported bindings. =cut @@ -215,7 +219,7 @@ sub binding { return; } -=head2 format($short_name) +=head2 format( $short_name ) Returns the full NameID Format URI for the given short name. @@ -235,9 +239,8 @@ sub format { elsif ($self->default_format) { return $self->formats->{$self->default_format}; } - else { - return; - } + + return; } __PACKAGE__->meta->make_immutable; diff --git a/lib/Net/SAML2/Protocol/ArtifactResolve.pm b/lib/Net/SAML2/Protocol/ArtifactResolve.pm index 7f523a6..b49c4e1 100644 --- a/lib/Net/SAML2/Protocol/ArtifactResolve.pm +++ b/lib/Net/SAML2/Protocol/ArtifactResolve.pm @@ -28,9 +28,21 @@ the given issuer and artifact. Arguments: - * issuer - the issuing SP's identity URI - * artifact - the artifact to be resolved - * destination - the IdP's identity URI +=over + +=item B + +issuing SP's identity URI + +=item B + +artifact to be resolved + +=item B + +IdP's identity URI + +=back =cut @@ -39,7 +51,7 @@ has 'issuer' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'destination' => (isa => Uri, is => 'ro', required => 1, coerce => 1); -=head2 as_xml +=head2 as_xml( ) Returns the ArtifactResolve request as XML. diff --git a/lib/Net/SAML2/Protocol/Assertion.pm b/lib/Net/SAML2/Protocol/Assertion.pm index fb2d9e4..3a39d80 100644 --- a/lib/Net/SAML2/Protocol/Assertion.pm +++ b/lib/Net/SAML2/Protocol/Assertion.pm @@ -36,6 +36,16 @@ has 'audience' => (isa => NonEmptySimpleStr, is => 'ro', required => 1); Constructor. Creates an instance of the Assertion object, parsing the given XML to find the attributes, session and nameid. +Arguments: + +=over + +=item B + +XML data + +=back + =cut sub new_from_xml { @@ -71,7 +81,7 @@ sub new_from_xml { return $self; } -=head2 name +=head2 name( ) Returns the CN attribute, if provided. diff --git a/lib/Net/SAML2/Protocol/AuthnRequest.pm b/lib/Net/SAML2/Protocol/AuthnRequest.pm index d496e58..cfead91 100644 --- a/lib/Net/SAML2/Protocol/AuthnRequest.pm +++ b/lib/Net/SAML2/Protocol/AuthnRequest.pm @@ -28,8 +28,17 @@ Constructor. Creates an instance of the AuthnRequest object. Arguments: - * issuer - the SP's identity URI - * destination - the IdP's identity URI +=over + +=item B + +SP's identity URI + +=item B + +IdP's identity URI + +=back =cut @@ -37,7 +46,7 @@ has 'issuer' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'destination' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'nameid_format' => (isa => NonEmptySimpleStr, is => 'ro', required => 1); -=head2 as_xml() +=head2 as_xml( ) Returns the AuthnRequest as XML. diff --git a/lib/Net/SAML2/Protocol/LogoutRequest.pm b/lib/Net/SAML2/Protocol/LogoutRequest.pm index cd40580..b10e0e1 100644 --- a/lib/Net/SAML2/Protocol/LogoutRequest.pm +++ b/lib/Net/SAML2/Protocol/LogoutRequest.pm @@ -26,11 +26,29 @@ Constructor. Returns an instance of the LogoutRequest object. Arguments: - * session - the session to log out - * nameid - the NameID of the user to log out - * nameid_format - the NameIDFormat to specify - * issuer - the SP's identity URI - * destination - the IdP's identity URI +=over + +=item B + +session to log out + +=item B + +NameID of the user to log out + +=item B + +NameIDFormat to specify + +=item B + +SP's identity URI + +=item B + +IdP's identity URI + +=back =cut @@ -40,10 +58,20 @@ has 'nameid_format' => (isa => NonEmptySimpleStr, is => 'ro', required => 1); has 'issuer' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'destination' => (isa => Uri, is => 'ro', required => 1, coerce => 1); -=head2 new_from_xml +=head2 new_from_xml( ... ) Create a LogoutRequest object from the given XML. +Arguments: + +=over + +=item B + +XML data + +=back + =cut sub new_from_xml { @@ -65,7 +93,7 @@ sub new_from_xml { return $self; } -=head2 as_xml() +=head2 as_xml( ) Returns the LogoutRequest as XML. diff --git a/lib/Net/SAML2/Protocol/LogoutResponse.pm b/lib/Net/SAML2/Protocol/LogoutResponse.pm index e9763b5..f75a3e9 100644 --- a/lib/Net/SAML2/Protocol/LogoutResponse.pm +++ b/lib/Net/SAML2/Protocol/LogoutResponse.pm @@ -26,10 +26,25 @@ Constructor. Returns an instance of the LogoutResponse object. Arguments: - * issuer - the SP's identity URI - * destination - the IdP's identity URI - * status - the response status - * response_to - the request ID we're responding to +=over + +=item B + +SP's identity URI + +=item B + +IdP's identity URI + +=item B + +response status + +=item B + +request ID we're responding to + +=back =cut @@ -38,10 +53,18 @@ has 'destination' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'status' => (isa => Str, is => 'ro', required => 1); has 'response_to' => (isa => Str, is => 'ro', required => 1); -=head2 new_from_xml +=head2 new_from_xml( ... ) Create a LogoutResponse object from the given XML. +Arguments: + +=over + +=item B + +XML data + =cut sub new_from_xml { @@ -63,7 +86,7 @@ sub new_from_xml { return $self; } -=head2 as_xml() +=head2 as_xml( ) Returns the LogoutResponse as XML. @@ -99,7 +122,7 @@ sub as_xml { ); } -=head2 success +=head2 success( ) Returns true if the Response's status is Success. diff --git a/lib/Net/SAML2/Role/ProtocolMessage.pm b/lib/Net/SAML2/Role/ProtocolMessage.pm index d34051f..84b2eb1 100644 --- a/lib/Net/SAML2/Role/ProtocolMessage.pm +++ b/lib/Net/SAML2/Role/ProtocolMessage.pm @@ -38,10 +38,22 @@ around 'BUILDARGS' => sub { =head1 METHODS -=head2 status_uri($status) +=head2 status_uri( $status ) Provides a mapping from short names for statuses to the full status URIs. +Legal short names for B<$status> are: + +=over + +=item C + +=item C + +=item C + +=back + =cut sub status_uri { diff --git a/lib/Net/SAML2/SP.pm b/lib/Net/SAML2/SP.pm index 93d6a4a..3f95a34 100644 --- a/lib/Net/SAML2/SP.pm +++ b/lib/Net/SAML2/SP.pm @@ -13,6 +13,7 @@ Net::SAML2::SP - SAML Service Provider object id => 'http://localhost:3000', url => 'http://localhost:3000', cert => 'sign-nopw-cert.pem', + key => 'sign-nopw-key.pem', ); =head1 METHODS @@ -28,20 +29,48 @@ Constructor. Create an SP object. Arguments: - * url - the base for all SP service URLs - * id - the SP's identity URI. - * cert - path to the signing certificate - * cacert - path to the CA certificate for verification +=over - * org_name - the SP organisation name - * org_display_name - the SP organisation display name - * org_contact - an SP contact email address +=item B + +base for all SP service URLs + +=item B + +SP's identity URI. + +=item B + +path to the signing certificate + +=item B + +path to the private key for the signing certificate + +=item B + +path to the CA certificate for verification + +=item B + +SP organisation name + +=item B + +SP organisation display name + +=item B + +SP contact email address + +=back =cut has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1); has 'id' => (isa => Str, is => 'ro', required => 1); has 'cert' => (isa => Str, is => 'ro', required => 1); +has 'key' => (isa => Str, is => 'ro', required => 1); has 'cacert' => (isa => Str, is => 'ro', required => 1); has 'org_name' => (isa => Str, is => 'ro', required => 1); @@ -61,7 +90,7 @@ sub BUILD { return $self; } -=head2 authn_request($destination) +=head2 authn_request( $destination, $nameid_format ) Returns an AuthnRequest object created by this SP, intended for the given destination, which should be the identity URI of the IdP. @@ -81,7 +110,7 @@ sub authn_request { return $authnreq; } -=head2 logout_request($destination, $nameid, $nameid_format, $session) +=head2 logout_request( $destination, $nameid, $nameid_format, $session ) Returns a LogoutRequest object created by this SP, intended for the given destination, which should be the identity URI of the IdP. @@ -104,7 +133,7 @@ sub logout_request { return $logout_req; } -=head2 logout_response($destination, $status, $response_to) +=head2 logout_response( $destination, $status, $response_to ) Returns a LogoutResponse object created by this SP, intended for the given destination, which should be the identity URI of the IdP. @@ -128,7 +157,7 @@ sub logout_response { return $logout_req; } -=head2 artifact_request($destination, $artifact) +=head2 artifact_request( $destination, $artifact ) Returns an ArtifactResolve request object created by this SP, intended for the given destination, which should be the identity URI of the @@ -149,11 +178,11 @@ sub artifact_request { return $artifact_request; } -=head2 sso_redirect_binding($idp, $param) +=head2 sso_redirect_binding( $idp, $param ) Returns a Redirect binding object for this SP, configured against the given IDP for Single Sign On. $param specifies the name of the query -parameter involved - typically SAMLRequest. +parameter involved - typically C. =cut @@ -163,18 +192,18 @@ sub sso_redirect_binding { 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->cert, + key => $self->key, param => $param, ); return $redirect; } -=head2 slo_redirect_binding +=head2 slo_redirect_binding( $idp, $param ) Returns a Redirect binding object for this SP, configured against the given IDP for Single Log Out. $param specifies the name of the query -parameter involved - typically SAMLRequest or SAMLResponse. +parameter involved - typically C or C. =cut @@ -184,14 +213,14 @@ sub slo_redirect_binding { my $redirect = Net::SAML2::Binding::Redirect->new( url => $idp->slo_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'), cert => $idp->cert('signing'), - key => $self->cert, + key => $self->key, param => $param, ); return $redirect; } -=head2 soap_binding +=head2 soap_binding( $ua, $idp_url, $idp_cert ) Returns a SOAP binding object for this SP, with a destination of the given URL and signing certificate. @@ -205,7 +234,7 @@ sub soap_binding { my $soap = Net::SAML2::Binding::SOAP->new( ua => $ua, - key => $self->cert, + key => $self->key, cert => $self->cert, url => $idp_url, idp_cert => $idp_cert, @@ -215,7 +244,7 @@ sub soap_binding { return $soap; } -=head2 post_binding +=head2 post_binding( ) Returns a POST binding object for this SP. @@ -231,7 +260,7 @@ sub post_binding { return $post; } -=head2 metadata +=head2 metadata( ) Returns the metadata XML document for this SP. diff --git a/lib/Net/SAML2/XML/Sig.pm b/lib/Net/SAML2/XML/Sig.pm index 265f499..09a1750 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 sha1_base64); use XML::XPath; use MIME::Base64; use Carp; @@ -157,10 +157,10 @@ 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 $digest_method = $self->{parser}->findvalue('dsig:SignedInfo/dsig:Reference/dsig:DigestMethod/@Algorithm', $signature_node); + my $digest = _trim($self->{parser}->findvalue('dsig:SignedInfo/dsig:Reference/dsig:DigestValue', $signature_node)); + + my $signed_xml = $self->_get_signed_xml($signature_node); my $canonical = $self->_transform($signed_xml, $signature_node); my $digest_bin = sha1($canonical); @@ -184,7 +184,9 @@ sub _get_xml_to_sign { sub _get_signed_xml { my $self = shift; - my $id = $self->{parser}->findvalue('//dsig:Signature/dsig:SignedInfo/dsig:Reference/@URI'); + my $context = shift; + + my $id = $self->{parser}->findvalue('dsig:SignedInfo/dsig:Reference/@URI', $context); $id =~ s/^#//; $self->{'sign_id'} = $id; my $xpath = "//*[\@ID='$id']"; @@ -355,7 +357,7 @@ sub _transform_env_sig { if (defined $self->{dsig_prefix} && length $self->{dsig_prefix}) { $prefix = $self->{dsig_prefix} . ':'; } - $str =~ s/(<${prefix}Signature(.*?)>(.*?)\<\/${prefix}Signature>)//igs; + $str =~ s/(<${prefix}Signature(.*?)>(.*?)\<\/${prefix}Signature>)//is; return $str; } @@ -747,7 +749,7 @@ Now, let's insert a signature: =over -=item L +=item L =item L diff --git a/t/02-create-sp.t b/t/02-create-sp.t index 95fb755..6e513cc 100644 --- a/t/02-create-sp.t +++ b/t/02-create-sp.t @@ -5,6 +5,7 @@ my $sp = Net::SAML2::SP->new( id => 'http://localhost:3000', url => 'http://localhost:3000', cert => 't/sign-nopw-cert.pem', + key => 't/sign-nopw-cert.pem', cacert => 't/cacert.pem', org_name => 'Test', org_display_name => 'Test', diff --git a/t/04-response.t b/t/04-response.t index 1b53eba..5a1b7c5 100644 --- a/t/04-response.t +++ b/t/04-response.t @@ -64,6 +64,7 @@ my $sp = Net::SAML2::SP->new( id => 'http://localhost:3000', url => 'http://localhost:3000', cert => 't/sign-nopw-cert.pem', + key => 't/sign-nopw-cert.pem', cacert => 't/cacert.pem', org_name => 'Test', org_display_name => 'Test', diff --git a/t/05-soap-binding.t b/t/05-soap-binding.t index f854250..d49ed40 100644 --- a/t/05-soap-binding.t +++ b/t/05-soap-binding.t @@ -10,6 +10,7 @@ my $sp = Net::SAML2::SP->new( id => 'http://localhost:3000', url => 'http://localhost:3000', cert => 't/sign-nopw-cert.pem', + key => 't/sign-nopw-cert.pem', cacert => 't/cacert.pem', org_name => 'Test', org_display_name => 'Test', diff --git a/t/06-redirect-binding.t b/t/06-redirect-binding.t index e22ce2a..1e9f4ad 100644 --- a/t/06-redirect-binding.t +++ b/t/06-redirect-binding.t @@ -10,6 +10,7 @@ use LWP::UserAgent; my $sp = Net::SAML2::SP->new( id => 'http://localhost:3000', url => 'http://localhost:3000', + key => 't/sign-nopw-cert.pem', cert => 't/sign-nopw-cert.pem', cacert => 't/cacert.pem', org_name => 'Test', diff --git a/testapp/lib/Saml2Test.pm b/testapp/lib/Saml2Test.pm index 833a460..a2c6ec9 100644 --- a/testapp/lib/Saml2Test.pm +++ b/testapp/lib/Saml2Test.pm @@ -153,6 +153,7 @@ sub _sp { id => 'http://localhost:3000', url => 'http://localhost:3000', cert => 'sign-nopw-cert.pem', + key => 'sign-nopw-cert.pem', cacert => 'saml_cacert.pem', org_name => 'Saml2Test',