Net-SAML2
view release on metacpan or search on metacpan
t/30-response-to-assertion-standalone.t view on Meta::CPAN
sub make_assertion {
my %p = @_;
my $id = $p{id} // "_legit_" . int(rand(1e9));
my $nameid = $p{nameid} // 'lowuser@victim.com';
my $now = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time - 60));
my $exp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time + 3600));
return <<"XML";
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="$id" Version="2.0" IssueInstant="$now">
<saml:Issuer>http://idp.test/idp</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">$nameid</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData InResponseTo="_req_$$" NotOnOrAfter="$exp" Recipient="http://sp.test/saml/post"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="$now" NotOnOrAfter="$exp">
<saml:AudienceRestriction>
<saml:Audience>http://sp.test</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="$now" SessionIndex="_sess_$$">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>user</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
XML
}
sub wrap_in_response {
my $inner = shift;
my $resp_id = "_resp_$$" . "_" . int(rand(1e9));
my $now = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime());
return <<"XML";
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="$resp_id" Version="2.0" IssueInstant="$now"
InResponseTo="NETSAML2_req$$"
Destination="http://sp.test/saml/post">
<saml:Issuer>http://idp.test/idp</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
$inner
</samlp:Response>
XML
}
# Sign, then move the <Signature> to right after <saml:Issuer> (schema
# position) without reserialising the signed element, so the digest stays
# valid. Same helper shape as t/32-xsw-defenses.t.
sub sign_schema_compliant {
my ($s, $xml) = @_;
my $signed = $s->sign($xml);
my ($sig) = $signed =~ m{(<(?:ds|dsig):Signature\b.*?</(?:ds|dsig):Signature>)}s;
return $signed unless $sig;
$signed =~ s{\Q$sig\E}{}s;
$signed =~ s{(</saml:Issuer>)}{$1$sig}s;
return $signed;
}
my $signer = XML::Sig->new({
x509 => 1,
key => $idp_key,
cert => $idp_crt,
no_xml_declaration => 1,
});
my $signed_response = wrap_in_response(
sign_schema_compliant($signer, make_assertion(nameid => 'lowuser@victim.com'))
);
# Response built with a trust anchor -> to_assertion inherits it, so the
# caller need not supply cacert a second time and a missing cacert cannot
# silently produce an unverified Assertion.
{
my $resp = Net::SAML2::Object::Response->new_from_xml(
xml => $signed_response,
cacert => $idp_crt,
);
my $assertion;
lives_ok(sub { $assertion = $resp->to_assertion },
'standalone to_assertion() succeeds without re-passing cacert') or diag $@;
isa_ok($assertion, 'Net::SAML2::Protocol::Assertion');
is($assertion && $assertion->cacert, $idp_crt,
'Assertion inherited the Response cacert');
is($assertion && $assertion->nameid, 'lowuser@victim.com',
'extracted the signed NameID through the inherited cacert path');
}
# Response built insecure -> the insecure posture flows through, so the
# Assertion does not croak for a missing cacert it was never going to use.
{
my $resp = Net::SAML2::Object::Response->new_from_xml(
xml => $signed_response,
insecure_trust_embedded_cert => 1,
);
my $assertion = $resp->to_assertion;
is($assertion->insecure_trust_embedded_cert, 1,
'Assertion inherited the Response insecure_trust_embedded_cert flag');
}
# Caller-supplied args override the inherited defaults: an insecure Response
# can still be upgraded to a real trust anchor at to_assertion time.
{
my $resp = Net::SAML2::Object::Response->new_from_xml(
xml => $signed_response,
insecure_trust_embedded_cert => 1,
);
my $assertion = $resp->to_assertion(cacert => $idp_crt);
is($assertion->cacert, $idp_crt,
'caller cacert overrides the inherited insecure posture');
}
done_testing;
( run in 0.984 second using v1.01-cache-2.11-cpan-5fbc6bb55f2 )