| Activator documentation | Contained in the Activator distribution. |
Catalyst::Plugin::SecureCookies - Tamper-resistant, encrypted HTTP Cookies
use Catalyst qw/SecureCookies/;
MyApp->config->{SecureCookies} = {
key => $blowfish_key,
ssl => 1 # send the checksum over ssl
};
MyApp->setup;
# later, in another part of MyApp...
$c->request->exp_secure_cookies( $expiration );
$c->request->set_secure_cookie( 'my_cookie_name', $value );
my $secure_data = $c->request->get_secure_cookie('my_cookie_name');
When HTTP cookies are used to store a user's state or identity it's important that your application is able to distinguish legitimate cookies from those that have been edited or created by a malicious user.
This module creates a pair of cookies which encrypt a form so the user cannot modify cookie contents.
SecureCookies is implemented using Crypt::CBC and MIME::Base64 to encrypt and encode a urlencoded string representing a perl hash. The encoded string is then hashed using Digest::SHA1 to prepare a sort of "checksum" or hash to make sure the user did not modify the cookie.
MyApp->config->{SecureCookies}->{key} = $secret_key;
This parameter is required, and sets the secret key that is used to encrypt the cookies with Crypt::CBC Blowfish. This needs to be a 16 hex character string.
MyApp->config->{SecureCookies}->{ssl} = 0;
# or
MyApp->config->{SecureCookies}->{ssl} = 1;
This parameter is optional, and will default to 1 if not set.
If 1, the checksum or hash cookie will be sent over SSL for
added security. This will prevent replay attacks from being
used against the server.
If 0, the checksum will be sent as a normal, non-secure cookie.
$c->request->get_secure_cookie($cookie_name)If a cookie was successfully authenticated then this method will return the value of the cookie.
Description: Takes a hashref representing web form elements, encrypts the components, creates a base64 safe url string
Args: $form_hasref - hashref of vars
Return: $encoded - the encoded form $csum - the checksum
Description: Takes a base64 safe url string representing form elements, decrypts the components, creates a hashref m Args: $encoded - encoded form $csum - csum for the form
Return: $form_hashref - hashref of the variables
Description: - safely encode using base64 to be used in urls
Description: - safely decode base64 from urls
Description:
convert a get string ( key1=val1&key2=val2 ) to a hash representing a web form. If you are really using a get string, you must be sure to only pass in the text after the question mark.
Args: $url_encoded - parse this text
Return: $form - hashref of the variables
Description: convert $string into a url safe format
Catalyst, Digest::SHA1, Crypt::CBC, MIME::Base64
http://www.schneier.com/blog/archives/2005/08/new_cryptanalyt.html
Rob Johnson rob@giant-rock.com
* Karim A. Nassar for converting this into a self-contained Catalyst Plugin. * All the helpful people in #catalyst.
Copyright (c) 2007 Karim A. Nassar <karim.nassar@acm.org>
You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file.
| Activator documentation | Contained in the Activator distribution. |
package Catalyst::Plugin::SecureCookies; use strict; use CGI::Cookie; use Symbol; use Class::Accessor::Fast; use Digest::SHA1; use Crypt::CBC; use MIME::Base64; our $VERSION = 0.01; our $CIPHER;
*{Symbol::qualify_to_ref('SecureCookies', 'Catalyst::Request')} = Class::Accessor::Fast::make_accessor('Catalyst::Request', 'SecureCookies');
*{Symbol::qualify_to_ref('get_secure_cookie', 'Catalyst::Request')} = sub { my $self = shift; my $name = shift; return $self->SecureCookies->{$name}; }; # add a secure cookie to the output *{Symbol::qualify_to_ref('set_secure_cookie', 'Catalyst::Response')} = sub { my $self = shift; my $name = shift; my $value = shift; $self->{SecureCookies}->{$name} = $value; }; ## set the cookie exp time *{Symbol::qualify_to_ref('exp_secure_cookies', 'Catalyst::Response')} = Class::Accessor::Fast::make_accessor('Catalyst::Request', 'exp_secure_cookies'); sub setup { my $self = shift; $self->config->{SecureCookies}->{ssl} ||= 1; return $self->NEXT::setup(@_); } # remove and check hash in Cookie Values sub prepare_cookies { my $c = shift; $c->NEXT::prepare_cookies(@_); ## pull out our secure dudes $c->request->{SecureCookies} = {}; my $rJ = $c->request->cookie( 'rJ' ); my $rC = $c->request->cookie( 'rC' ); ## decrypt them if( $rJ && $rC ) { ## decode it my $secret_form = &_decrypt( $c, $rJ->value, # encoded cookie $rC->value ); # it's checksum if( $secret_form ) { foreach my $key (keys %$secret_form) { $c->request->{SecureCookies}->{$key} = $secret_form->{$key}; } } } return $c; } # alter all Cookie Values to include a hash sub finalize_cookies { my $c = shift; my $sc = $c->response->{SecureCookies}; if( $sc ) { ## pull in the existing secure cookies my $sco = $c->req->SecureCookies; if( $sco ) { foreach my $key (keys %$sco) { if( ! defined($sc->{$key}) ) { $sc->{$key} = $sco->{$key}; } } } ## first encode the form my ($encoded, $csum) = &_encrypt( $c, $sc ); ## ssl, yes or no? my $ssl = $c->config->{SecureCookies}->{ssl}; my $ssl_val = $ssl ? 1 : 0; ## expiration? my $exp = $c->response->exp_secure_cookies(); ## make the two cookies $c->response->cookies->{rJ} = { value => $encoded, expires => $exp }; $c->response->cookies->{rC} = { value => $csum, expires => $exp }; my $domain = $c->config->{SecureCookies}->{cookie_domain}; if( $domain ) { $c->response->cookies->{rJ}->{domain} = ".$domain"; $c->response->cookies->{rJ}->{path} = '/'; $c->response->cookies->{rC}->{domain} = ".$domain"; $c->response->cookies->{rC}->{path} = '/'; } } $c->NEXT::finalize_cookies(@_); return $c; }
sub _encrypt { my ( $c, $form_hashref ) = @_; my $cipher = &_get_cipher( $c->config->{SecureCookies}->{key} ); ## first url encode it my $encoded = &_url_encode_hashref( $form_hashref ); ## now we encrypt and mime encode it my $encrypted = $cipher->encrypt( $encoded ); # $encrypted =~ s/^RandomIV//; my $mimed = &_base64_encode_url( $encrypted ); ## checksum it my $ctx = new Digest::SHA1; $ctx->add( $mimed ); my $csum = substr( &_base64_encode_url( $ctx->digest ), 3, 4 ); ## give em what they want return ($mimed, $csum); }
sub _decrypt { my ( $c, $encoded, $csum ) = @_; my $cipher = &_get_cipher( $c->config->{SecureCookies}->{key} ); ## calc a csum for the encrypted block my $ctx = new Digest::SHA1; $ctx->add( $encoded ); my $this_csum = substr( &_base64_encode_url( $ctx->digest ), 3, 4 ); ## compare it if( $csum ne $this_csum ) { return undef; } ## ok, the csum is good, decrypt my $encrypted = &_base64_decode_url( $encoded ); # $encrypted = "RandomIV".$encrypted; my $dec = $cipher->decrypt( $encrypted ); ## get the form my $form_hashref = &_url_decode_hashref( $dec ); return $form_hashref; }
sub _base64_encode_url { my ($data, $separator) = @_; my $mimed = encode_base64( $data, $separator ); ## convert to web friendlies $mimed =~ s/\s//g; $mimed =~ tr/[\+\/\=]/[\_\-.]/; return $mimed; }
sub _base64_decode_url { my ($mimed) = @_; ## convert from web friendlies $mimed =~ tr/[\_\-.]/[\+\/\=]/; return decode_base64( $mimed ); } sub _get_cipher { my $key = shift; if ( !$CIPHER ) { $CIPHER = new Crypt::CBC( -key => pack("H16", $key), -cipher => 'Blowfish' ); } return $CIPHER; } sub _url_encode_hashref { my ($form_hashref) = @_; ## bail if it's not a form if( !defined($form_hashref) ) { return ''; } ## run through the data, convert my @pairs; foreach my $key (keys %{$form_hashref}) { ## grab the value my $val = $form_hashref->{$key}; ## support for array my @vals; if( ref($val) eq 'ARRAY' ) { @vals = @$val; } else { push( @vals, $val ); } ## encode the key and val foreach my $val1 (@vals) { my $keye = &_urlencode_string( defined ( $key ) ? $key : '' ); my $vale = &_urlencode_string( defined ( $val1 ) ? $val1 : '' ); ## save push( @pairs, "$keye=$vale" ); } } ## return the string return join( "&", @pairs ); }
sub _url_decode_hashref { my ($url_encoded) = @_; my %form; foreach (split(/&/,$url_encoded)) { ## convert plus's to spaces s/\+/ /g; ## split into key and value. my ($key, $val) = split(/\=/,$_,2 ); ## convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge if $key; $val =~ s/%(..)/pack("c",hex($1))/ge if $val; ## associate key and value, multiple vars get tab delimination $form{$key} .= "\t" if ( defined($form{$key}) ); $form{$key} .= $val if ( defined($val) ); } return \%form; }
sub _urlencode_string { my ($string) = @_; ## standard urlencode $string =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg; $string =~ s/ /\+/g; return $string; }
1;