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 )