CGI-SecureState

 view release on metacpan or  search on metacpan

SecureState.pm  view on Meta::CPAN

	do { $param=pop(@data) } while ($param ne "Saved-Values" && @data);

	#check to make sure that our mindset is the same as the statefile's
	$param=pop @data;
	if ($param ne ($isforgetful ? "Forgetful" : "Remembering")) {
	    $self->errormsg('statefile inconsistent with mindset') }

	while (@data) {
	    ($param = pop @data) =~ s/\\(.)/$1/go; #unescape meta-characters
	    @values=split(/(?<!\\)\ \ /, pop @data);
	    next if (!$isforgetful && (exists($memory->{$param}) || defined $self->param($param)));
	    foreach $value (@values) { $value =~ s/\\(.)/$1/go } #unescape meta-characters
	    $self->param($param,@values);
	    $self->{'.memory'}->{$param}=1 if ($isforgetful);
	}
    }
}


#The encipher subroutine accepts a list of values to encrypt and writes them to
#the state file.  If the list of values is empty, it merely updates the timestamp
#of the state file.
sub encipher
{
    my ($self, $buffer) = @_;
    my ($cipher, $statefile) = @$self{'.cipher','.statefile'};
    my ($length, $time, $block);
    $time=pack("N",time());

    # Open the target file and die with warnings if necessary
    my $open_flags = $buffer ? (O_WRONLY | O_TRUNC | O_CREAT) : (O_RDWR | O_CREAT);
    if ($AVOID_SYMLINKS && -l $statefile) { $self->errormsg('symlink encountered') }
    sysopen(STATEFILE, $statefile, $open_flags, 0600 ) or $self->errormsg('failed to open the state file');
    if ($USE_FLOCK && !flock(STATEFILE, LOCK_EX)) { $self->errormsg('failed to lock the state file') }
    binmode STATEFILE;

    #if we've got nothing to write, only update the timestamp
    unless ($buffer) {
	if (sysread(STATEFILE,$buffer,16)==16) {
	    #the length of the encrypted data is stored in the first four bytes of the state file
	    $length=substr($cipher->decrypt(substr($buffer,0,8)),0,4);
	    $buffer=$length.($time^substr($buffer,12,4));
	} else {
	    $length=pack("N",0);
	    $buffer=$length.$time;
	}
	sysseek(STATEFILE,0,$SEEK_SET);
	syswrite(STATEFILE,$cipher->encrypt($buffer));
    }
    else {
	#add metadata to the beginning of the plaintext
	$length=length($buffer);
	$buffer=pack("N",$length).$time.$buffer;

	#pad the buffer to have a length that is divisible by 8
	if ($length%=8) {
	    $length=8-$length;
	    $buffer.=chr(int(rand(256))) while ($length--);
	}

	#encrypt in reverse-CBC mode
	$block=$cipher->encrypt(substr($buffer,-8,8));
	substr($buffer,-8,8,$block);

	$length=length($buffer) - 8;
	while(($length-=8)>-8) {
	    $block^=substr($buffer,$length,8);
	    $block=$cipher->encrypt($block);
	    substr($buffer,$length,8,$block);
	}

	#blast it to the file
	syswrite(STATEFILE,$buffer);
    }
    if ($USE_FLOCK) { flock(STATEFILE, LOCK_UN) || $self->errormsg('failed to unlock the state file') }
    close(STATEFILE) || $self->errormsg('failed to close the state file');
}


sub decipher
{
    my $self = shift;
    my ($cipher,$statefile) = @$self{'.cipher','.statefile'};
    my ($length,$extra,$decoded,$buffer,$block);

    if ($AVOID_SYMLINKS) { -l $statefile and $self->errormsg('symlink encountered')}
    sysopen(STATEFILE,$statefile, O_RDONLY) || $self->errormsg('failed to open the state file');
    if ($USE_FLOCK) { flock(STATEFILE, LOCK_SH) || $self->errormsg('failed to lock the state file') }
    binmode STATEFILE;

    #read metadata
    sysread(STATEFILE,$block,8);
    $block = $cipher->decrypt($block);

    #if there is nothing in the file, only set the age; otherwise read the contents
    unless (sysread(STATEFILE,$buffer,8)==8) {
	$self->{'.age'} = unpack("N",substr($block,4,4));
	$buffer = "";
    } else {
	#parse metadata
	$block^=$buffer;
	$self->{'.age'} = unpack("N",substr($block,4,4));
	$length = unpack("N",substr($block,0,4));
	$extra = ($length % 8) ? (8-($length % 8)) : 0;
	$decoded=-8;

	#sanity check
	if ((stat(STATEFILE))[7] != ($length+$extra+8))
	{ $self->errormsg('invalid state file') }

	#read the rest of the file
	sysseek(STATEFILE, 8, $SEEK_SET);
	unless (sysread(STATEFILE,$buffer,$length+$extra) == ($length+$extra))
	{ $self->errormsg('invalid state file') }

	my $next_block;
	$block = $cipher->decrypt(substr($buffer,0,8));
	#decrypt it
	while (($decoded+=8)<$length-8) {
	    $next_block = substr($buffer,$decoded+8,8);
	    $block^=$next_block;



( run in 0.489 second using v1.01-cache-2.11-cpan-e1769b4cff6 )