Postini-SAML

 view release on metacpan or  search on metacpan

lib/Postini/SAML.pm  view on Meta::CPAN

package Postini::SAML;
{
  $Postini::SAML::VERSION = '0.001';
}

use warnings;
use strict;

use Crypt::OpenSSL::RSA;
use MIME::Base64 qw( encode_base64 );
use XML::Spice;
use Date::Format qw( time2str );
use Data::Random qw( rand_chars );
use XML::CanonicalizeXML;
use Digest::SHA1 qw( sha1 );
use Carp qw( croak );

# Postini SAML ACS
my $ACS_URL = 'https://pfs.postini.com/pfs/spServlet';

sub get_acs_url {
    return $ACS_URL;
}

sub new {
    my ($class, $arg) = @_;

    my @missing = grep { not exists $arg->{$_} } qw( keyfile certfile issuer );
    if ( @missing )
    {
        croak "missing args: " . join( q{ }, @missing );
    }
    
    my $self = bless {}, $class;

    $self->_load_rsa_key( $arg->{'keyfile'}, $arg->{'certfile'} );

    $self->{'issuer'} = $arg->{'issuer'};

    return $self;
}

sub _load_rsa_key {
    my ($self, $key_file, $cert_file) = @_;

    # load the keyfile and prepare a context for signing
    open my $key_fh, '<', $key_file or croak "couldn't open $key_file for reading: $!";
    my $key_text = do { local $/; <$key_fh> };
    close $key_fh;

    my $key = Crypt::OpenSSL::RSA->new_private_key( $key_text );
    if ( not $key )
    {
        croak "failed to instantiate Crypt::OpenSSL::RSA object from $key_file";
    }

    $key->use_pkcs1_padding();
    $self->{'key'} = $key;

    # we need to include the certificate without headers in the signed XML, so
    # extract it
    open my $cert_fh, '<', $cert_file or croak "couldn't open $cert_file for reading: $!";
    my $cert_text = do { local $/; <$cert_fh> };
    close $cert_fh;

    my ($cert_pem) = $cert_text =~ m{
        -----BEGIN\sCERTIFICATE-----
        (.+)
        -----END\sCERTIFICATE-----
    }smx;
    $cert_pem =~ s{ [\r\n]+ }{}smxg;

    # build a XML fragment containing the key info. this will be included in
    # the signature XML
    $self->{'key_info_xml'} =
        x('ds:KeyInfo',
            x('ds:X509Data',
                x('ds:X509Certificate', $cert_pem),
            ),
        ),
    ;
}

# return the current signature xml (actually XML::Spice chunk). deliberately
# returns undef if its not available, causing it to be ignored during chunk
# expansion
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

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 0.635 second using v1.00-cache-2.02-grep-82fe00e-cpan-2cc899e4a130 )