#!perl use strict; use warnings; use Test::More; use FindBin; use File::Spec; use lib File::Spec->catdir($FindBin::Bin, 'test-lib'); use Time::Local qw(timegm); use AuthenNZigovtTestHelper; require Authen::NZigovt; my $conf_dir = test_conf_dir(); my $sp = Authen::NZigovt->service_provider( conf_dir => $conf_dir ); isa_ok($sp, 'Authen::NZigovt::ServiceProvider'); my $req = $sp->new_request(); isa_ok($req, 'Authen::NZigovt::AuthenRequest'); my $req_id = $req->request_id; like($req_id, qr{^\w{16,}$}, "request id comprises at least 16 'word' chars"); like($req_id, qr{^\D}, "request id does not start with a digit"); is($req->entity_id, $sp->entity_id, 'request entity_id matches SP'); my($year, $month, $day, $hour, $min, $sec) = $req->request_time =~ qr{^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$}; ok(defined($sec), 'format of request time looks good'); my $req_time = timegm($sec, $min, $hour, $day, $month - 1, $year - 1900); ok((time() - $req_time) < 10, 'request time seems to be a UTC version of current time'); is($req->destination_url, $sp->idp->single_signon_location, 'request destination URL matches IdP metadata setting'); is($req->relay_state, undef, 'request has no default relay state'); is($req->allow_create, 'false', 'request does not enable account creation by default'); is($req->force_auth, 'true', 'request does force logon by default'); my $strength = $req->auth_strength; isa_ok($strength, 'Authen::NZigovt::LogonStrength'); eval { $strength->assert_match('low', 'exact'); }; is($@, '', "default auth strength is low"); my $url = $req->as_url; my($idp_url, $payload, $sig_alg, $sig) = $url =~ m{ ^(https://.*?)[?] SAMLRequest=(.*?)& SigAlg=(.*?)& Signature=(.*?)(?:$|&) }x; ok(defined($sig), 'format of request as URL looks good'); is($idp_url, $sp->idp->single_signon_location, 'host and path are correct'); my $plaintext = "SAMLRequest=$payload&SigAlg=$sig_alg"; ($payload, $sig_alg, $sig) = map { s{%([0-9a-f]{2})}{chr(hex($1))}ieg; $_; } ($payload, $sig_alg, $sig); my $b64chr = '[A-Za-z0-9+/]'; like($payload, qr/^$b64chr{200,}=*$/, 'request payload is base64 encoded'); is($sig_alg, 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', "signature algorithm is correct"); like($sig, qr/^$b64chr{200,}=*$/, 'signature is base64 encoded'); my $cert_path = File::Spec->catfile(test_conf_dir(), 'sp-sign-crt.pem'); my $signer = Authen::NZigovt->class_for('xml_signer')->new( pub_cert_file => $cert_path, ); ok($signer->_verify_rsa_signature($plaintext, $sig), 'signature verified successfully using public key'); my $xml = Authen::NZigovt::AuthenRequest->_request_from_uri($url); ok($xml, 'extracted XML request for analysis'); xml_found_node_ok($xml, q{/nssamlp:AuthnRequest}); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@Version} => '2.0'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@AssertionConsumerServiceIndex} => '0'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@ForceAuthn} => 'true'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@ID} => $req_id); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@IssueInstant} => $req->request_time); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@Destination} => $sp->idp->single_signon_location); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssaml:Issuer} => $sp->entity_id); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssamlp:NameIDPolicy/@AllowCreate} => 'false'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssamlp:NameIDPolicy/@Format} => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssamlp:RequestedAuthnContext/nssaml:AuthnContextClassRef} => 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:LowStrength'); my $req2 = $sp->new_request( allow_create => 1, force_auth => 0, relay_state => 'pending', auth_strength => 'sms', ); is($req2->allow_create, 'true', 'request enables account creation'); is($req2->force_auth, 'false', 'request does not force logon'); is($req2->relay_state, 'pending', 'request has expected relay state'); $strength = $req2->auth_strength; isa_ok($strength, 'Authen::NZigovt::LogonStrength'); eval { $strength->assert_match('mod', 'minimum'); }; is($@, '', "auth strength is at least 'moderate'"); eval { $strength->assert_match('mod', 'exact'); }; is($@, '', "auth strength is 'moderate'"); eval { $strength->assert_match('sms', 'exact'); }; is($@, '', "auth strength is 'moderate-SMS'"); $url = $req2->as_url; my($relay); ($idp_url, $payload, $relay, $sig_alg, $sig) = $url =~ m{ ^(https://.*?)[?] SAMLRequest=(.*?)& RelayState=(.*?)& SigAlg=(.*?)& Signature=(.*?)(?:$|&) }x; ok(defined($sig), 'format of request as URL looks good'); is($relay, 'pending', 'RelayState parameter looks good'); $plaintext = "SAMLRequest=$payload&RelayState=$relay&SigAlg=$sig_alg"; $sig =~ s{%([0-9a-f]{2})}{chr(hex($1))}ieg; ok($signer->_verify_rsa_signature($plaintext, $sig), 'signature verified successfully using public key'); $xml = Authen::NZigovt::AuthenRequest->_request_from_uri($url); ok($xml, 'extracted XML request for analysis'); xml_found_node_ok($xml, q{/nssamlp:AuthnRequest}); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/@ForceAuthn} => 'false'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssamlp:NameIDPolicy/@AllowCreate} => 'true'); xml_node_content_is($xml, q{/nssamlp:AuthnRequest/nssamlp:RequestedAuthnContext/nssaml:AuthnContextClassRef} => 'urn:nzl:govt:ict:stds:authn:deployment:GLS:SAML:2.0:ac:classes:ModStrength::OTP:Token:SMS'); done_testing();