package OpenSocialX::Shindig::Crypter; our $VERSION = '0.03'; # ABSTRACT: OpenSocial Shindig Crypter use URI::Escape qw/uri_escape uri_unescape/; use MIME::Base64 qw/decode_base64 encode_base64/; use Crypt::CBC; use Digest::SHA; # Key used for time stamp (in seconds) of data my $TIMESTAMP_KEY = 't'; # allow three minutes for clock skew my $CLOCK_SKEW_ALLOWANCE = 180; sub new { my $class = shift; my $cfg = defined $_[0] && ref( $_[0] ) eq 'HASH' ? shift : {@_}; # validate $cfg->{cipher} or die 'cipher key is required'; $cfg->{hmac} or die 'hmac key is required'; $cfg->{iv} or die 'iv key is required'; ( length( $cfg->{cipher} ) == 16 ) or die 'cipher key must be 16 chars'; ( length( $cfg->{iv} ) == 16 ) or die 'iv key must be 16 chars'; return bless $cfg, $class; } sub wrap { my ( $self, $in ) = @_; my $encoded = _serializeAndTimestamp($in); my $cipher = Crypt::CBC->new( { 'key' => $self->{cipher}, 'cipher' => 'Rijndael', 'iv' => $self->{iv}, 'literal_key' => 1, 'padding' => 'null', 'header' => 'none', keysize => 128 / 8, } ); my $cipherText = $cipher->encrypt($encoded); my $hmac = Digest::SHA::hmac_sha1( $cipherText, $self->{hmac} ); my $b64 = encode_base64( $cipherText . $hmac ); return $b64; } sub _serializeAndTimestamp { my ($in) = @_; my $encoded; foreach my $key ( keys %$in ) { $encoded .= uri_escape($key) . "=" . uri_escape( $in->{$key} ) . "&"; } $encoded .= $TIMESTAMP_KEY . "=" . time(); return $encoded; } sub unwrap { my ( $self, $in, $max_age ) = @_; my $bin = decode_base64($in); my $cipherText = substr( $bin, 0, -20 ); my $hmac = substr( $bin, length($cipherText) ); # verify my $v_hmac = Digest::SHA::hmac_sha1( $cipherText, $self->{hmac} ); if ( $v_hmac ne $hmac ) { die 'HMAC verification failure'; } my $cipher = Crypt::CBC->new( { 'key' => $self->{cipher}, 'cipher' => 'Rijndael', 'iv' => $self->{iv}, 'literal_key' => 1, 'padding' => 'null', 'header' => 'none', keysize => 128 / 8, } ); my $plain = $cipher->decrypt($cipherText); my $out = $self->deserialize($plain); $self->checkTimestamp( $out, $max_age ); return $out; } sub deserialize { my ( $self, $plain ) = @_; my $h; my @items = split( /[\&\=]/, $plain ); my $i; for ( $i = 0 ; $i < scalar(@items) ; ) { my $key = uri_unescape( $items[ $i++ ] ); my $value = uri_unescape( $items[ $i++ ] ); $h->{$key} = $value; } return $h; } sub checkTimestamp { my ( $self, $out, $max_age ) = @_; my $minTime = $out->{$TIMESTAMP_KEY} - $CLOCK_SKEW_ALLOWANCE; my $maxTime = $out->{$TIMESTAMP_KEY} + $max_age + $CLOCK_SKEW_ALLOWANCE; my $now = time(); if ( !( $minTime < $now && $now < $maxTime ) ) { die "Security token expired"; } } my $OWNER_KEY = "o"; my $APP_KEY = "a"; my $VIEWER_KEY = "v"; my $DOMAIN_KEY = "d"; my $APPURL_KEY = "u"; my $MODULE_KEY = "m"; sub create_token { my $self = shift; my $data = defined $_[0] && ref( $_[0] ) eq 'HASH' ? shift : {@_}; my $token_data = { $OWNER_KEY => $data->{owner}, $APP_KEY => $data->{app}, $VIEWER_KEY => $data->{viewer}, $DOMAIN_KEY => $data->{domain}, $APPURL_KEY => $data->{app_url}, $MODULE_KEY => $data->{module_id}, }; my $token = $self->wrap($token_data); return uri_escape($token); } 1; __END__ =head1 NAME OpenSocialX::Shindig::Crypter - OpenSocial Shindig Crypter =head1 VERSION version 0.03 =head1 SYNOPSIS use OpenSocialX::Shindig::Crypter; my $crypter = OpenSocialX::Shindig::Crypter->new( { cipher => 'length16length16', hmac => 'forhmac_sha1', iv => 'anotherlength16k' } ); my $token = $crypter->create_token( { owner => $owner_id, viewer => $viewer_id, app => $app_id, app_url => $app_url, domain => $domain, module_id => $module_id } ); =head1 DESCRIPTION Apache Shindig L is an OpenSocial container and helps you to start hosting OpenSocial apps quickly by providing the code to render gadgets, proxy requests, and handle REST and RPC requests. From the article L, we know that we can do 'Application' things in Perl. basically the stuff will be =over 4 =item * use Perl L (this module) to create B encrypted token through C =item * the php C will unwrap the token and validate it. The file is in the C dir of this .tar.gz or you can download it from L you can copy it to the dir of C defined in shindig/config/container.php, it will override the default C provided by shindig. and the last thing is to defined the same keys in shindig/config/container.php like: 'token_cipher_key' => 'length16length16', 'token_hmac_key' => 'forhmac_sha1', 'token_iv_key' => 'anotherlength16k', remember that C is new =back =head2 METHODS =over 4 =item * new my $crypter = OpenSocialX::Shindig::Crypter->new( { cipher => 'length16length16', hmac => 'forhmac_sha1', iv => 'anotherlength16k' } ); C and C must be 16 chars. =item * create_token my $token = $crypter->create_token( { owner => $owner_id, viewer => $viewer_id, app => $app_id, app_url => $app_url, domain => $domain, module_id => $module_id } ); if you don't know what C is, you can leave it alone. =item * wrap my $encrypted = $crypter->wrap({ a => 1, c => 3, o => 5 } ); encrypt the hash by L and L and encode_base64 it =item * unwrap my $hash = $crypter->unwrap($encrypted); decrypt the above data =item * deserialize =item * checkTimestamp =item * _serializeAndTimestamp =back =head2 EXAMPLE use URI::Escape; use MIME::Base64; use OpenSocialX::Shindig::Crypter; my $crypter = OpenSocialX::Shindig::Crypter->new( { cipher => $config->{opensocial}->{cipherKey}, hmac => $config->{opensocial}->{hmacKey}, iv => $config->{opensocial}->{ivKey}, } ); my $security_token = uri_escape( encode_base64( $crypter->create_token( { owner => $owner_id, viewer => $viwer_id, app => $gadget->{id}, domain => $config->{opensocial}->{container}, app_url => $gadget->{url}, } ) ) ); # later in tt2 or others # st=$security_token =head1 AUTHOR Fayland Lam =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2009 by Fayland Lam. This is free software; you can redistribute it and/or modify it under the same terms as perl itself. =pod