Postini-SAML
view release on metacpan or search on metacpan
lib/Postini/SAML.pm view on Meta::CPAN
sub _get_cached_signature_xml {
my ($self) = @_;
return $self->{'signature_xml'};
}
# generate a valid, signed response and return it
sub get_response_xml {
my ($self, $mail) = @_;
if ( not $mail )
{
croak "required email address not provided";
}
# INPUT:
# T, text-to-be-signed, a byte string;
# Ks, RSA private key;
#
# 1. Canonicalize the text-to-be-signed, C = C14n(T).
# 2. Compute the message digest of the canonicalized text, m = Hash(C).
# 3. Encapsulate the message digest in an XML <SignedInfo> element, SI, in canonicalized form.
# 4. Compute the RSA signatureValue of the canonicalized <SignedInfo> element, SV = RsaSign(Ks, SI).
# 5. Compose the final XML document including the signatureValue, this time in non-canonicalized form.
# get rid of any cached signature
delete $self->{'signature_xml'};
# get the response data and canonicalise it
my $response_xml = $self->_response_xml( $mail );
my $canonical_response_xml = $self->_canonicalize_xml( $response_xml );
# compute digest
my $response_digest = encode_base64( sha1( $canonical_response_xml ), q{} );
# create a canonical signed info fragment
my $signed_info_xml = $self->_signed_info_xml( $response_digest );
my $canonical_signed_info_xml = $self->_canonicalize_xml( $signed_info_xml );
# create the signature
my $signature = encode_base64( $self->{'key'}->sign( $canonical_signed_info_xml ), q{} );
# now create the signature xml fragment
$self->{'signature_xml'} = $self->_signature_xml( $signed_info_xml, $signature );;
# force the response chunk to be regenerated which will cause the
# signature to be included
$response_xml->forget;
# stringify and return
return "".$response_xml;
}
# generate a signature XML fragment, including the signature metadata fragment
# and the raw signature
sub _signature_xml {
my ($self, $signed_info_xml, $signature) = @_;
my $signature_xml =
x('ds:Signature',
{
'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#',
},
$signed_info_xml,
x('ds:SignatureValue', $signature),
$self->{'key_info_xml'},
),
;
return $signature_xml;
}
# generate a signature metadata XML fragement, including the message digest
sub _signed_info_xml {
my ($self, $digest) = @_;
my $signed_info_xml =
x('ds:SignedInfo',
{
# we must include all the namespaces in use anywhere in the
# document so they can be included in the signature
'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#',
'xmlns:saml' => 'urn:oasis:names:tc:SAML:1.0:assertion',
'xmlns:samlp' => 'urn:oasis:names:tc:SAML:1.0:protocol',
},
x('ds:CanonicalizationMethod',
{
'Algorithm' => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
},
),
x('ds:SignatureMethod',
{
'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
},
),
x('ds:Reference',
{
'URI' => "",
},
x('ds:Transforms',
x('ds:Transform',
{
'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
},
),
),
x('ds:DigestMethod',
{
'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1',
}
),
x('ds:DigestValue', $digest),
),
),
;
return $signed_info_xml;
}
# build the SAML response, including the signature if available
sub _response_xml {
my ($self, $mail) = @_;
my $now = time();
my $issue_instant = time2str( '%Y-%m-%dT%XZ', $now, 'UTC' );
# assertion is valid for 60 seconds
my $not_on_or_after = time2str( '%Y-%m-%dT%XZ', $now+60, 'UTC' );
# first character must not be a number to match xsd:ID
my $response_id = join q{}, 'z', rand_chars( 'set' => 'alphanumeric', 'size' => 40 );
my $assertion_id = join q{}, 'z', rand_chars( 'set' => 'alphanumeric', 'size' => 40 );
my $name_id = join q{}, 'z', rand_chars( 'set' => 'alphanumeric', 'size' => 40 );
my $response_xml =
x('samlp:Response',
{
'xmlns:saml' => 'urn:oasis:names:tc:SAML:1.0:assertion',
'xmlns:samlp' => 'urn:oasis:names:tc:SAML:1.0:protocol',
'MajorVersion' => '1',
'MinorVersion' => '1',
'IssueInstant' => $issue_instant,
'ResponseID' => $response_id,
'Recipient' => $ACS_URL,
},
# include the signature if its available. if not then it wil be
# undef and will be ignored
sub { $self->{'signature_xml'} },
x('samlp:Status',
x('samlp:StatusCode',
{
'Value' => 'samlp:Success',
},
),
),
x('saml:Assertion',
{
'MajorVersion' => '1',
'MinorVersion' => '1',
'IssueInstant' => $issue_instant,
'AssertionID' => $assertion_id,
'Issuer' => $self->{'issuer'},
},
( run in 1.076 second using v1.01-cache-2.11-cpan-71847e10f99 )