File-KDBX

 view release on metacpan or  search on metacpan

lib/File/KDBX/Safe.pm  view on Meta::CPAN

        else {
            throw 'Safe strings must be a hashref or stringref', type => ref $string;
        }
        push @{$self->{items}}, $item;
        $self->{index}{refaddr($string)} = $item;
        $self->{counter} += length($item->{val});
    }

    return $self;
}


sub lock_protected { shift->add_protected(@_) }

sub add_protected {
    my $self = shift;
    my $filter = is_coderef($_[0]) ? shift : undef;
    my @strings = map { is_arrayref($_) ? @$_ : $_ } @_;

    @strings or throw 'Must provide strings to lock';

    for my $string (@strings) {
        my $item = {str => $string, off => $self->{counter}};
        $item->{filter} = $filter if defined $filter;
        if (is_scalarref($string)) {
            next if !defined $$string;
            $item->{val} = $$string;
            erase $string;
        }
        elsif (is_hashref($string)) {
            next if !defined $string->{value};
            $item->{val} = $string->{value};
            erase \$string->{value};
        }
        else {
            throw 'Safe strings must be a hashref or stringref', type => ref $string;
        }
        push @{$self->{items}}, $item;
        $self->{index}{refaddr($string)} = $item;
        $self->{counter} += length($item->{val});
    }

    return $self;
}


sub unlock {
    my $self = shift;

    my $cipher = $self->cipher;
    $cipher->finish;
    $self->{counter} = 0;

    for my $item (@{$self->{items}}) {
        my $string  = $item->{str};
        my $cleanup = erase_scoped \$item->{val};
        my $str_ref;
        if (is_scalarref($string)) {
            $$string = $cipher->crypt(\$item->{val});
            if (my $encoding = $item->{enc}) {
                my $decoded = decode($encoding, $string->{value});
                erase $string;
                $$string = $decoded;
            }
            $str_ref = $string;
        }
        elsif (is_hashref($string)) {
            $string->{value} = $cipher->crypt(\$item->{val});
            if (my $encoding = $item->{enc}) {
                my $decoded = decode($encoding, $string->{value});
                erase \$string->{value};
                $string->{value} = $decoded;
            }
            $str_ref = \$string->{value};
        }
        else {
            die 'Unexpected';
        }
        if (my $filter = $item->{filter}) {
            my $filtered = $filter->($$str_ref);
            erase $str_ref;
            $$str_ref = $filtered;
        }
    }

    return $self->clear;
}


sub peek {
    my $self = shift;
    my $string = shift;

    my $item = $self->{index}{refaddr($string)} // return;

    my $cipher = $self->cipher->dup(offset => $item->{off});

    my $value = $cipher->crypt(\$item->{val});
    if (my $encoding = $item->{enc}) {
        my $decoded = decode($encoding, $value);
        erase $value;
        return $decoded;
    }
    return $value;
}


sub cipher {
    my $self = shift;
    $self->{cipher} //= do {
        require File::KDBX::Cipher;
        File::KDBX::Cipher->new(stream_id => STREAM_ID_CHACHA20, key => random_bytes(64));
    };
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

File::KDBX::Safe - Keep strings encrypted while in memory

=head1 VERSION

version 0.906

=head1 SYNOPSIS

    use File::KDBX::Safe;

    $safe = File::KDBX::Safe->new;

    my $msg = 'Secret text';
    $safe->add(\$msg);
    # $msg is now undef, the original message no longer in RAM

    my $obj = { value => 'Also secret' };
    $safe->add($obj);
    # $obj is now { value => undef }

    say $safe->peek($msg);  # Secret text

    $safe->unlock;
    say $msg;               # Secret text
    say $obj->{value};      # Also secret

=head1 DESCRIPTION

This module provides memory protection functionality. It keeps strings encrypted in memory and decrypts them
as-needed. Encryption and decryption is done using a L<File::KDBX::Cipher::Stream>.

A safe can protect one or more (possibly many) strings. When a string is added to a safe, it gets added to an
internal list so it will be decrypted when the entire safe is unlocked.

=head1 ATTRIBUTES

=head2 cipher



( run in 0.759 second using v1.01-cache-2.11-cpan-39bf76dae61 )