1 package File
::KDBX
::Safe
;
2 # ABSTRACT: Keep strings encrypted while in memory
7 use Crypt
::PRNG
qw(random_bytes);
8 use Devel
::GlobalDestruction
;
9 use Encode
qw(encode decode);
10 use File
::KDBX
::Constants
qw(:random_stream);
11 use File
::KDBX
::Error
;
12 use File
::KDBX
::Util
qw(erase erase_scoped);
13 use Ref
::Util
qw(is_arrayref is_coderef is_hashref is_scalarref);
14 use Scalar
::Util
qw(refaddr);
17 our $VERSION = '999.999'; # VERSION
21 $safe = File
::KDBX
::Safe-
>new(%attributes);
22 $safe = File
::KDBX
::Safe-
>new(\
@strings, %attributes);
24 Create a new safe
for storing secret strings encrypted
in memory
.
26 If a cipher
is passed
, its stream will be
reset.
32 my %args = @_ % 2 == 0 ? @_ : (strings
=> shift, @_);
34 if (!$args{cipher
} && $args{key
}) {
35 require File
::KDBX
::Cipher
;
36 $args{cipher
} = File
::KDBX
::Cipher-
>new(stream_id
=> STREAM_ID_CHACHA20
, key
=> $args{key
});
39 my $self = bless \
%args, $class;
40 $self->cipher->finish;
43 my $strings = delete $args{strings
};
46 $self->add($strings) if $strings;
51 sub DESTROY
{ local ($., $@, $!, $^E, $?); !in_global_destruction
and $_[0]->unlock }
57 Clear a safe
, removing all store contents permanently
. Returns itself to allow
method chaining
.
73 $safe = $safe->lock(@strings);
74 $safe = $safe->lock(\
@strings);
76 Add one
or more strings to the memory protection stream
. Returns itself to allow
method chaining
.
80 sub lock { shift-
>add(@_) }
84 my @strings = map { is_arrayref
($_) ? @$_ : $_ } @_;
86 @strings or throw
'Must provide strings to lock';
88 my $cipher = $self->cipher;
90 for my $string (@strings) {
91 my $item = {str
=> $string, off
=> $self->{counter
}};
92 if (is_scalarref
($string)) {
93 next if !defined $$string;
94 $item->{enc
} = 'UTF-8' if utf8
::is_utf8
($$string);
95 if (my $encoding = $item->{enc
}) {
96 my $encoded = encode
($encoding, $$string);
97 $item->{val
} = $cipher->crypt(\
$encoded);
101 $item->{val
} = $cipher->crypt($string);
105 elsif (is_hashref
($string)) {
106 next if !defined $string->{value
};
107 $item->{enc
} = 'UTF-8' if utf8
::is_utf8
($string->{value
});
108 if (my $encoding = $item->{enc
}) {
109 my $encoded = encode
($encoding, $string->{value
});
110 $item->{val
} = $cipher->crypt(\
$encoded);
114 $item->{val
} = $cipher->crypt(\
$string->{value
});
116 erase \
$string->{value
};
119 throw
'Safe strings must be a hashref or stringref', type
=> ref $string;
121 push @{$self->{items
}}, $item;
122 $self->{index}{refaddr
($string)} = $item;
123 $self->{counter
} += length($item->{val
});
129 =method lock_protected
131 =method add_protected
133 $safe = $safe->lock_protected(@strings);
134 $safe = $safe->lock_protected(\
@strings);
136 Add strings that are already encrypted
. Returns itself to allow
method chaining
.
138 B
<WARNING
:> The cipher must be the same as was used to originally encrypt the strings
. You must add
139 already-encrypted strings
in the order
in which they were original encrypted
or they will
not decrypt
140 correctly
. You almost certainly
do not want to add both unprotected
and protected strings to a safe
.
144 sub lock_protected
{ shift-
>add_protected(@_) }
148 my $filter = is_coderef
($_[0]) ? shift : undef;
149 my @strings = map { is_arrayref
($_) ? @$_ : $_ } @_;
151 @strings or throw
'Must provide strings to lock';
153 for my $string (@strings) {
154 my $item = {str
=> $string};
155 $item->{filter
} = $filter if defined $filter;
156 if (is_scalarref
($string)) {
157 next if !defined $$string;
158 $item->{val
} = $$string;
161 elsif (is_hashref
($string)) {
162 next if !defined $string->{value
};
163 $item->{val
} = $string->{value
};
164 erase \
$string->{value
};
167 throw
'Safe strings must be a hashref or stringref', type
=> ref $string;
169 push @{$self->{items
}}, $item;
170 $self->{index}{refaddr
($string)} = $item;
171 $self->{counter
} += length($item->{val
});
179 $safe = $safe->unlock;
181 Decrypt all the strings
. Each stored string
is set to its original value
, potentially overwriting any value
182 that might have been set after locking the string
(so you probably should avoid modification to strings
while
183 locked
). The safe
is implicitly cleared
. Returns itself to allow
method chaining
.
185 This happens automatically
when the safe
is garbage-collected
.
192 my $cipher = $self->cipher;
194 $self->{counter
} = 0;
196 for my $item (@{$self->{items
}}) {
197 my $string = $item->{str
};
198 my $cleanup = erase_scoped \
$item->{val
};
200 if (is_scalarref
($string)) {
201 $$string = $cipher->crypt(\
$item->{val
});
202 if (my $encoding = $item->{enc
}) {
203 my $decoded = decode
($encoding, $string->{value
});
209 elsif (is_hashref
($string)) {
210 $string->{value
} = $cipher->crypt(\
$item->{val
});
211 if (my $encoding = $item->{enc
}) {
212 my $decoded = decode
($encoding, $string->{value
});
213 erase \
$string->{value
};
214 $string->{value
} = $decoded;
216 $str_ref = \
$string->{value
};
221 if (my $filter = $item->{filter
}) {
222 my $filtered = $filter->($$str_ref);
224 $$str_ref = $filtered;
233 $string_value = $safe->peek($string);
237 Peek into the safe at a particular string without decrypting the whole safe
. A copy of the string
is returned
,
238 and in order to ensure integrity of the memory protection you should erase the copy
when you
're done.
240 Returns C<undef> if the given C<$string> is not in memory protection.
248 my $item = $self->{index}{refaddr($string)} // return;
250 my $cipher = $self->cipher->dup(offset => $item->{off});
252 my $value = $cipher->crypt(\$item->{val});
253 if (my $encoding = $item->{enc}) {
254 my $decoded = decode($encoding, $value);
263 $cipher = $safe->cipher;
265 Get the L<File::KDBX::Cipher::Stream> protecting a safe.
271 $self->{cipher} //= do {
272 require File::KDBX::Cipher;
273 File::KDBX::Cipher->new(stream_id => STREAM_ID_CHACHA20, key => random_bytes(64));
282 use File::KDBX::Safe;
284 $safe = File::KDBX::Safe->new;
286 my $msg = 'Secret text
';
288 # $msg is now undef, the original message no longer in RAM
290 my $obj = { value => 'Also secret
' };
292 # $obj is now { value => undef }
294 say $safe->peek($msg); # Secret text
297 say $msg; # Secret text
298 say $obj->{value}; # Also secret
302 This module provides memory protection functionality. It keeps strings encrypted in memory and decrypts them
303 as-needed. Encryption and decryption is done using a L<File::KDBX::Cipher::Stream>.
305 A safe can protect one or more (possibly many) strings. When a string is added to a safe, it gets added to an
306 internal list so it will be decrypted when the entire safe is unlocked.