Skip to content

Commit 9c5585b

Browse files
authored
Merge pull request #37 from timlegge/testapp
Testapp fixes and PingIdentity Compatibility
2 parents 013c6ed + c51ba51 commit 9c5585b

File tree

18 files changed

+539
-118
lines changed

18 files changed

+539
-118
lines changed

README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ DESCRIPTION
8080
Mircosoft ADFS
8181
Keycloak
8282
Auth0 (requires Net::SAML2 >=0.39)
83+
PingIdentity
8384

8485
NAME
8586
Net::SAML2 - SAML bindings and protocol implementation

TUTORIAL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ this results in the following XML
463463
ISvnu1/xomsSS+aenG5toWmhoJIKFbfhQkpnBlgGD5+12Cxn2jHpgv15262ZZIJS
464464
WPp/0bQqdAAUzkJZPpUGUN1sTXPJexYT6na7XvLd6mvO1g+WDk6aZnW/zcT3T9tL
465465
Iavyic/p4gZtXckweq+VTn9CdZp6ZTQtVw==
466-
466+
467467
</ds:X509Certificate>
468468
</ds:X509Data>
469469
</ds:KeyInfo>

lib/Net/SAML2.pm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ Identity Providers (IdPs). It has been tested against:
9898
9999
=item Auth0 (requires Net::SAML2 >=0.39)
100100
101+
=item PingIdentity
102+
101103
=back
102104
103105
=head1 MAJOR CAVEATS

lib/Net/SAML2/Binding/Redirect.pm

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Net::SAML2::Binding::Redirect
1717
url => $sso_url, # Service Provider Single Sign Out URL
1818
param => 'SAMLRequest' OR 'SAMLResponse', # Type of request
1919
cert => $idp->cert('signing') # Identity Provider (IdP) certificate
20+
sig_hash => 'sha1', 'sha224', 'sha256', 'sha384', 'sha512' # Signature to sign request
2021
);
2122
2223
my $url = $redirect->sign($authnreq);
@@ -64,6 +65,16 @@ IdP's SSO (Single Sign Out) service url for the Redirect binding
6465
6566
query param name to use (SAMLRequest, SAMLResponse)
6667
68+
=item B<sig_hash>
69+
70+
RSA hash to use to sign request
71+
72+
Supported:
73+
74+
sha1, sha224, sha256, sha384, sha512
75+
76+
sha1 is current default but will change by version 44
77+
6778
=back
6879
6980
=cut
@@ -72,6 +83,7 @@ has 'key' => (isa => 'Str', is => 'ro', required => 1);
7283
has 'cert' => (isa => 'Str', is => 'ro', required => 1);
7384
has 'url' => (isa => Uri, is => 'ro', required => 1, coerce => 1);
7485
has 'param' => (isa => 'Str', is => 'ro', required => 1);
86+
has 'sig_hash' => (isa => 'Str', is => 'ro', required => 0);
7587

7688
=head2 sign( $request, $relaystate )
7789
@@ -96,11 +108,30 @@ sub sign {
96108
my $u = URI->new($self->url);
97109
$u->query_param($self->param, $req);
98110
$u->query_param('RelayState', $relaystate) if defined $relaystate;
99-
$u->query_param('SigAlg', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1');
100111

101112
my $key_string = read_file($self->key);
102113
my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key($key_string);
103114

115+
if ( exists $self->{ sig_hash } && grep { $_ eq $self->{ sig_hash } } ('sha224', 'sha256', 'sha384', 'sha512'))
116+
{
117+
if ($self->{ sig_hash } eq 'sha224') {
118+
$rsa_priv->use_sha224_hash;
119+
} elsif ($self->{ sig_hash } eq 'sha256') {
120+
$rsa_priv->use_sha256_hash;
121+
} elsif ($self->{ sig_hash } eq 'sha384') {
122+
$rsa_priv->use_sha384_hash;
123+
} elsif ($self->{ sig_hash } eq 'sha512') {
124+
$rsa_priv->use_sha512_hash;
125+
} else {
126+
die "Unsupported Signing Hash";
127+
}
128+
$u->query_param('SigAlg', 'http://www.w3.org/2001/04/xmldsig-more#rsa-' . $self->{ sig_hash });
129+
}
130+
else { #$self->{ sig_hash } eq 'sha1' or something unsupported
131+
$rsa_priv->use_sha1_hash;
132+
$u->query_param('SigAlg', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1');
133+
}
134+
104135
my $to_sign = $u->query;
105136
my $sig = encode_base64($rsa_priv->sign($to_sign), '');
106137
$u->query_param('Signature', $sig);
@@ -123,12 +154,24 @@ sub verify {
123154

124155
# verify the response
125156
my $sigalg = $u->query_param('SigAlg');
126-
die "can't verify '$sigalg' signatures"
127-
unless $sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
128157

129158
my $cert = Crypt::OpenSSL::X509->new_from_string($self->cert);
130159
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($cert->pubkey);
131160

161+
if ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256') {
162+
$rsa_pub->use_sha256_hash;
163+
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha224') {
164+
$rsa_pub->use_sha224_hash;
165+
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384') {
166+
$rsa_pub->use_sha384_hash;
167+
} elsif ($sigalg eq 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512') {
168+
$rsa_pub->use_sha512_hash;
169+
} elsif ($sigalg eq 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
170+
$rsa_pub->use_sha1_hash;
171+
} else {
172+
die "Unsupported Signature Algorithim: $sigalg";
173+
}
174+
132175
my $sig = decode_base64($u->query_param_delete('Signature'));
133176
my $signed = $u->query;
134177
die "bad sig" unless $rsa_pub->verify($signed, $sig);

lib/Net/SAML2/Protocol/LogoutRequest.pm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ SP's identity URI
4747
4848
=item B<destination>
4949
50-
IdP's identity URI
50+
IdP's identity URI this is required for a signed message but likely should be
51+
sent regardless
5152
5253
=back
5354
@@ -56,6 +57,7 @@ IdP's identity URI
5657
has 'session' => (isa => NonEmptySimpleStr, is => 'ro', required => 1);
5758
has 'nameid' => (isa => NonEmptySimpleStr, is => 'ro', required => 1);
5859
has 'nameid_format' => (isa => NonEmptySimpleStr, is => 'ro', required => 1);
60+
has 'destination' => (isa => NonEmptySimpleStr, is => 'ro', required => 0);
5961

6062
=head2 new_from_xml( ... )
6163
@@ -112,6 +114,7 @@ sub as_xml {
112114
$samlp,
113115
{ ID => $self->id,
114116
IssueInstant => $self->issue_instant,
117+
Destination => $self->destination,
115118
Version => '2.0' },
116119
$x->Issuer(
117120
$saml,

lib/Net/SAML2/SP.pm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,11 @@ sub metadata {
339339
{ Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
340340
Location => $self->url . '/saml/sls-redirect-response' },
341341
),
342+
$x->SingleLogoutService(
343+
$md,
344+
{ Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
345+
Location => $self->url . '/saml/sls-post-response' },
346+
),
342347
$x->AssertionConsumerService(
343348
$md,
344349
{ Binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',

xt/testapp/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
metadata-*.xml
2+
saml_cacert-*.pem
3+
logs/*

xt/testapp/README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Saml2Test Application
2+
3+
The Saml2Test application was created to allow the developers to test a SAML2 Service Provider (SP) application against an Identity Provider (IdP). The application allows you to:
4+
5+
1. Produce a SP metadata.xml that can be uploaded to an Identity Provider
6+
2. Login via a SAML2 AuthnRequest
7+
3. Access user attributes provided in the SAML2 Assertion
8+
3. Logout via a SAML2 LogoutRequest
9+
10+
## Required Steps
11+
12+
### Create host file entry
13+
14+
The config.yml is configured for the testapp to be available at: https://netsaml2-testapp.local. Add the following to your /etc/hosts entry (or the equivalent on windows).
15+
16+
127.0.0.1 netsaml2-testapp.local netsaml2-testapp
17+
18+
### Generate new Service Provider (SP) signing Key and Certificate
19+
20+
This is optional - you can generate your own certificates or use the existing certificates from the git repository.
21+
22+
1. openssl req -x509 -nodes -newkey rsa:4096 -keyout sign-private.pem -out sign-certonly.pem -days 36500
23+
2. cat sign-certonly.pem sign-private.pem > sign-nopw-cert.pem
24+
25+
### Start the Saml2Test application
26+
27+
1. cd xt/testapp
28+
2. perl Saml2Test.pl
29+
30+
The application starts and accepts browser connections on port 3000:
31+
32+
Access http://localhost:3000
33+
34+
### Run lighttpd to proxy https to the Saml2Test application
35+
36+
Many SAML2 Identity Providers will not allow the application (Service Provider) URL to be http and force you to specify https to use SAML2. lighttpd is used to listen on port 443 and use https protocol so that the Identity Provider can redirect or POST to a https site. lighttpd then proxies that communication to the Dancer application listening on port 3000.
37+
38+
1. cd xt/testapp
39+
2. sudo lighttpd -D -f lighttpd.conf
40+
41+
Note that the command requires sudo to allow it to use the default https port of 443.
42+
43+
TODO: maybe change it to use 8443
44+
45+
### Create your metadata.xml file
46+
47+
Download the metadata for you configured application from your Identity Provider and save it to:
48+
49+
xt/testapp/metadata.xml
50+
51+
### Run lighttpd to deliver metadata.xml
52+
53+
Net::SAML2 requires access to a URL containing the metadata. The simplest method to provide this is to run the provided lighttpd-metadata.conf file:
54+
55+
1. cd xt/testapp
56+
2. lighttpd -D -f lighttpd-metadata.conf
57+
58+
The metadata has been configured to be available at: http://localhost:8880/metadata.xml.
59+
60+
Note that the configuration attempts to only deliver a file named metadata.xml from the xt/testapp directory. There are no guarantees - this is a test application so verify your own security.
61+
62+
### Access the testapp to download the application metadata
63+
64+
Saml2Test provides a metadata.xml for the Application that can be used to upload to the Identity Provider to make the configuration simpler.
65+
66+
1. Access http://localhost:3000
67+
2. Click *SP Metadata* to download the metadata.xml
68+
3. Save the metadata.xml file for upload to the Identity Provider
69+
70+
### Configure your Identity Provider
71+
72+
Depending on the Identity Provider this can range from simple to easy. For testing purposes most Identity Providers will provide a free developer account. Some require you to define users first, others will simply allow you to use whatever your admin user is as a SAML user.
73+
74+
If there is an option to upload the metadata.xml that is probably your first step as it will set most configuration items properly for you.
75+
76+
Saml2Test expects the Identity Provider to provide an assertion with the following values:
77+
78+
1. DN
79+
2. CN
80+
3. EmailAddress
81+
4. FirstName
82+
5. Address
83+
6. Phone
84+
7. EmployeeNumber
85+
86+
Note that DN and CN (and others) may not be available. That can be customized in views/user.tt if you want to choose other options. However the Identity Provider must provide the assertion attributes that match the expected names in views/user.tt.
87+
88+
## Debugging
89+
90+
If you are making changes to Net::SAML2 and want to use the Saml2Test to test those changes do the following:
91+
92+
1. Make the changes as required in Net::SAML2
93+
2. perl Makefile.PL
94+
3. make
95+
4. cd xt/testapp
96+
5. perl -I ../../blib/lib/ -I ../../blib/arch/ Saml2Test.pl
97+
98+
That allows you to test against the version of Net::SAML2 that you are modifying. Note that Dancer caches the version it started with including the Net::SAML2 module so you will need to restart Saml2Test.pl to test the changes you made.

xt/testapp/config.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ layout: "main"
22
#logger: "console"
33
appname: "Saml2Test"
44

5-
idp: "http://sso.dev.venda.com/opensso/saml2/jsp/exportmetadata.jsp"
5+
idp: "http://localhost:8880/metadata.xml"
6+
issuer: "https://netsaml2-testapp.local"
7+
url: "https://netsaml2-testapp.local"
8+
cert: "sign-certonly.pem"
9+
key: "sign-nopw-cert.pem"
10+
cacert: "saml_cacert.pem"

xt/testapp/lib/Saml2Test.pm

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package # PAUSE hide
33
use strict;
44
use warnings;
55

6-
=head1 NAME
6+
=head1 NAME
77
88
Saml2Test - test Dancer app for Net::SAML2
99
@@ -16,6 +16,7 @@ Demo app to show use of Net::SAML2 as an SP.
1616
use Dancer ':syntax';
1717
use Net::SAML2;
1818
use MIME::Base64 qw/ decode_base64 /;
19+
use URI::Encode;
1920

2021
our $VERSION = '0.1';
2122

@@ -27,7 +28,7 @@ get '/login' => sub {
2728
my $idp = _idp();
2829
my $sp = _sp();
2930
my $authnreq = $sp->authn_request(
30-
$idp->entityid,
31+
$idp->sso_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
3132
$idp->format, # default format.
3233
)->as_xml;
3334

@@ -47,7 +48,10 @@ get '/logout-redirect' => sub {
4748
my $sp = _sp();
4849

4950
my $logoutreq = $sp->logout_request(
50-
$idp->entityid, params->{nameid}, $idp->format, params->{session}
51+
$idp->slo_url('urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'),
52+
params->{nameid},
53+
$idp->format,
54+
params->{session}
5155
)->as_xml;
5256

5357
my $redirect = $sp->slo_redirect_binding($idp, 'SAMLRequest');
@@ -88,7 +92,7 @@ post '/consumer-post' => sub {
8892
my $ret = $post->handle_response(
8993
params->{SAMLResponse}
9094
);
91-
95+
9296
if ($ret) {
9397
my $assertion = Net::SAML2::Protocol::Assertion->new_from_xml(
9498
xml => decode_base64(params->{SAMLResponse})
@@ -123,7 +127,7 @@ get '/consumer-artifact' => sub {
123127
my $assertion = Net::SAML2::Protocol::Assertion->new_from_xml(
124128
xml => $response
125129
);
126-
130+
127131
template 'user', { assertion => $assertion };
128132
}
129133
else {
@@ -137,26 +141,56 @@ get '/sls-redirect-response' => sub {
137141

138142
my $sp = _sp();
139143
my $redirect = $sp->slo_redirect_binding($idp, 'SAMLResponse');
140-
my ($response, $relaystate) = $redirect->verify(request->request_uri);
141-
144+
145+
my $uri = URI::Encode->new( { encode_reserved => 0 } );
146+
my ($response, $relaystate) = $redirect->verify($uri->decode(request->request_uri));
147+
142148
redirect $relaystate || '/', 302;
143149
return "Redirected\n";
144150
};
145151

152+
post '/sls-post-response' => sub {
153+
my $idp = _idp();
154+
my $idp_cert = $idp->cert('signing');
155+
156+
my $sp = _sp();
157+
my $post = $sp->post_binding(cacert => $idp_cert);
158+
159+
my $ret = $post->handle_response(
160+
params->{SAMLResponse},
161+
);
162+
163+
if ($ret) {
164+
my $logout = Net::SAML2::Protocol::LogoutResponse->new_from_xml(
165+
xml => decode_base64(params->{SAMLResponse})
166+
);
167+
if ($logout->status eq 'urn:oasis:names:tc:SAML:2.0:status:Success') {
168+
print STDERR "Logout Success Status\n";
169+
}
170+
}
171+
else {
172+
return "<html><pre>Bad Logout Response</pre></html>";
173+
}
174+
175+
redirect '/', 302;
176+
return "Redirected\n";
177+
};
178+
146179
get '/metadata.xml' => sub {
180+
content_type 'application/octet-stream';
147181
my $sp = _sp();
148182
return $sp->metadata;
149183
};
150184

151185
sub _sp {
152186
my $sp = Net::SAML2::SP->new(
153-
id => 'http://localhost:3000',
154-
url => 'http://localhost:3000',
155-
cert => 'sign-nopw-cert.pem',
156-
key => 'sign-nopw-cert.pem',
157-
cacert => 'saml_cacert.pem',
187+
id => config->{issuer},
188+
url => config->{url},
189+
cert => config->{cert},
190+
key => config->{key},
191+
cacert => config->{cacert},
158192

159-
org_name => 'Saml2Test',
193+
org_name => 'Net::SAML2 Saml2Test',
160194
org_display_name => 'Saml2Test app for Net::SAML2',
161195
org_contact => 'saml2test@example.com',
162196
);

0 commit comments

Comments
 (0)