package File::KDBX;
# ABSTRACT: Encrypted database to store secret text and files
+use 5.010;
use warnings;
use strict;
use List::Util qw(any first);
use Ref::Util qw(is_ref is_arrayref is_plain_hashref);
use Scalar::Util qw(blessed);
-use Time::Piece;
+use Time::Piece 1.33;
use boolean;
use namespace::clean;
has 'meta.database_description_changed' => sub { gmtime }, coerce => \&to_time;
has 'meta.default_username' => '', coerce => \&to_string;
has 'meta.default_username_changed' => sub { gmtime }, coerce => \&to_time;
-has 'meta.maintenance_history_days' => 0, coerce => \&to_number;
+has 'meta.maintenance_history_days' => HISTORY_DEFAULT_MAX_AGE, coerce => \&to_number;
has 'meta.color' => '', coerce => \&to_string;
has 'meta.master_key_changed' => sub { gmtime }, coerce => \&to_time;
has 'meta.master_key_change_rec' => -1, coerce => \&to_number;
least C<KDBX_VERSION_4_0> (i.e. C<0x00040000>) because Argon2 was introduced with KDBX4.
This method never returns less than C<KDBX_VERSION_3_1> (i.e. C<0x00030001>). That file version is so
-ubiquitious and well-supported, there are seldom reasons to dump in a lesser format nowadays.
+ubiquitous and well-supported, there are seldom reasons to dump in a lesser format nowadays.
B<WARNING:> If you dump a database with a minimum version higher than the current L</version>, the dumper will
typically issue a warning and automatically upgrade the database. This seems like the safest behavior in order
Every database has only a single root group at a time. Some old KDB files might have multiple root groups.
When reading such files, a single implicit root group is created to contain the actual root groups. When
writing to such a format, if the root group looks like it was implicitly created then it won't be written and
-the resulting file might have multiple root groups. This allows working with older files without changing
-their written internal structure while still adhering to modern semantics while the database is opened.
+the resulting file might have multiple root groups, as it was before loading. This allows working with older
+files without changing their written internal structure while still adhering to modern semantics while the
+database is opened.
The root group of a KDBX database contains all of the database's entries and other groups. If you replace the
root group, you are essentially replacing the entire database contents with something else.
L<File::KDBX::Group/add_group> on the parent group, forwarding the arguments. Available options:
=for :list
-* C<group> (aka C<parent>) - Group object or group UUID to add the group to (default: root group)
+* C<group> - Group object or group UUID to add the group to (default: root group)
=cut
my %args = @_;
# find the right group to add the group to
- my $parent = delete $args{group} // delete $args{parent} // $self->root;
+ my $parent = delete $args{group} // $self->root;
$parent = $self->groups->grep({uuid => $parent})->next if !ref $parent;
$parent or throw 'Invalid group';
my %args = @_ % 2 == 0 ? @_ : (base => shift, @_);
my $base = delete $args{base} // $self->root;
- return $base->groups_deeply(%args);
+ return $base->all_groups(%args);
}
##############################################################################
L<File::KDBX::Group/add_entry> on the parent group, forwarding the arguments. Available options:
=for :list
-* C<group> (aka C<parent>) - Group object or group UUID to add the entry to (default: root group)
+* C<group> - Group object or group UUID to add the entry to (default: root group)
=cut
my %args = @_;
# find the right group to add the entry to
- my $parent = delete $args{group} // delete $args{parent} // $self->root;
+ my $parent = delete $args{group} // $self->root;
$parent = $self->groups->grep({uuid => $parent})->next if !ref $parent;
$parent or throw 'Invalid group';
my %args = @_ % 2 == 0 ? @_ : (base => shift, @_);
my $base = delete $args{base} // $self->root;
- return $base->entries_deeply(%args);
+ return $base->all_entries(%args);
}
##############################################################################
my %args = @_ % 2 == 0 ? @_ : (base => shift, @_);
my $base = delete $args{base} // $self->root;
- return $base->objects_deeply(%args);
+ return $base->all_objects(%args);
}
sub __iter__ { $_[0]->objects }
$kdbx->lock;
-Encrypt all protected binaries strings in a database. The encrypted strings are stored in
-a L<File::KDBX::Safe> associated with the database and the actual strings will be replaced with C<undef> to
+Encrypt all protected strings and binaries in a database. The encrypted data is stored in
+a L<File::KDBX::Safe> associated with the database and the actual values will be replaced with C<undef> to
indicate their protected state. Returns itself to allow method chaining.
-You can call C<code> on an already-locked database to memory-protect any unprotected strings and binaries
+You can call C<lock> on an already-locked database to memory-protect any unprotected strings and binaries
added after the last time the database was locked.
=cut
$kdbx->unlock;
-Decrypt all protected strings in a database, replacing C<undef> placeholders with unprotected values. Returns
-itself to allow method chaining.
+Decrypt all protected strings and binaries in a database, replacing C<undef> value placeholders with their
+actual, unprotected values. Returns itself to allow method chaining.
=cut
See L</lock> and L</unlock>.
+Example:
+
+ {
+ my $guard = $kdbx->unlock_scoped;
+ ...;
+ }
+ # $kdbx is now memory-locked
+
=cut
sub unlock_scoped {
$bool = $kdbx->is_locked;
-Get whether or not a database's strings are memory-protected. If this is true, then some or all of the
-protected strings within the database will be unavailable (literally have C<undef> values) until L</unlock> is
-called.
+Get whether or not a database's contents are in a locked (i.e. memory-protected) state. If this is true, then
+some or all of the protected strings and binaries within the database will be unavailable (literally have
+C<undef> values) until L</unlock> is called.
=cut
-sub is_locked { $_[0]->_safe ? 1 : 0 }
+sub is_locked { !!$_[0]->_safe }
##############################################################################
my $max_items = $args{max_items} // $self->history_max_items // HISTORY_DEFAULT_MAX_ITEMS;
my $max_size = $args{max_size} // $self->history_max_size // HISTORY_DEFAULT_MAX_SIZE;
- my $max_age = $args{max_age} // HISTORY_DEFAULT_MAX_AGE;
+ my $max_age = $args{max_age} // $self->maintenance_history_days // HISTORY_DEFAULT_MAX_AGE;
my @removed;
$self->entries->each(sub {
$key = $kdbx->key($primitive);
Get or set a L<File::KDBX::Key>. This is the master key (e.g. a password or a key file that can decrypt
-a database). See L<File::KDBX::Key/new> for an explanation of what the primitive can be.
+a database). You can also pass a primitive castable to a B<Key>. See L<File::KDBX::Key/new> for an explanation
+of what the primitive can be.
You generally don't need to call this directly because you can provide the key directly to the loader or
dumper when loading or dumping a KDBX file.
$key = $kdbx->composite_key($key);
$key = $kdbx->composite_key($primitive);
-Construct a L<File::KDBX::Key::Composite> from a primitive. See L<File::KDBX::Key/new> for an explanation of
-what the primitive can be. If the primitive does not represent a composite key, it will be wrapped.
+Construct a L<File::KDBX::Key::Composite> from a B<Key> or primitive. See L<File::KDBX::Key/new> for an
+explanation of what the primitive can be. If the primitive does not represent a composite key, it will be
+wrapped.
-You generally don't need to call this directly. The parser and writer use it to transform a master key into
+You generally don't need to call this directly. The loader and dumper use it to transform a master key into
a raw encryption key.
=cut
If not passed, the UUID comes from C<< $kdbx->headers->{cipher_id} >> and the encryption IV comes from
C<< $kdbx->headers->{encryption_iv} >>.
-You generally don't need to call this directly. The parser and writer use it to decrypt and encrypt KDBX
+You generally don't need to call this directly. The loader and dumper use it to decrypt and encrypt KDBX
files.
=cut
C<< $kdbx->inner_headers->{inner_random_stream_key} >> and
C<< $kdbx->inner_headers->{inner_random_stream_id} >> (respectively) for KDBX4 files.
-You generally don't need to call this directly. The parser and writer use it to scramble protected strings.
+You generally don't need to call this directly. The loader and dumper use it to scramble protected strings.
=cut
The UUID of a cipher used to encrypt the database when stored as a file.
-See L</File::KDBX::Cipher>.
+See L<File::KDBX::Cipher>.
=attr compression_flags
Timestamp indicating when the default username was last changed.
-=attr maintenance_history_days
-
-TODO... not really sure what this is. 😀
-
=attr color
A color associated with the database (in the form C<#ffffff> where "f" is a hexidecimal digit). Some agents
Number of days until the agent should prompt to force changing the master key.
Note: This is purely advisory. It is up to the individual agent software to actually enforce it.
-C<File::KDBX> does NOT enforce it.
+B<File::KDBX> does NOT enforce it.
=attr custom_icons
=attr recycle_bin_changed
-Timestamp indicating when the recycle bin was last changed.
+Timestamp indicating when the recycle bin group was last changed.
=attr entry_templates_group
=attr history_max_items
-The maximum number of historical entries allowed to be saved for each entry.
+The maximum number of historical entries that should be kept for each entry. Default is 10.
=attr history_max_size
-The maximum total size (in bytes) that each individual entry's history is allowed to grow.
+The maximum total size (in bytes) that each individual entry's history is allowed to grow. Default is 6 MiB.
+
+=attr maintenance_history_days
+
+The maximum age (in days) historical entries should be kept. Default it 365.
=attr settings_changed
use File::KDBX;
+ # Create a new database from scratch
my $kdbx = File::KDBX->new;
+ # Add some objects to the database
my $group = $kdbx->add_group(
name => 'Passwords',
);
-
my $entry = $group->add_entry(
title => 'My Bank',
+ username => 'mreynolds',
password => 's3cr3t',
);
- $kdbx->dump_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+ # Save the database to the filesystem
+ $kdbx->dump_file('passwords.kdbx', 'masterpw changeme');
- $kdbx = File::KDBX->load_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+ # Load the database from the filesystem into a new database instance
+ my $kdbx2 = File::KDBX->load_file('passwords.kdbx', 'masterpw changeme');
- $kdbx->entries->each(sub {
- my ($entry) = @_;
+ # Iterate over database entries, print entry titles
+ $kdbx2->entries->each(sub($entry, @) {
say 'Entry: ', $entry->title;
});
=head1 DESCRIPTION
-B<File::KDBX> provides everything you need to work with a KDBX database. A KDBX database is a hierarchical
+B<File::KDBX> provides everything you need to work with KDBX databases. A KDBX database is a hierarchical
object database which is commonly used to store secret information securely. It was developed for the KeePass
password safe. See L</"Introduction to KDBX"> for more information about KDBX.
-This module lets you query entries, create new entries, delete entries and modify entries. The distribution
-also includes various parsers and generators for serializing and persisting databases.
+This module lets you query entries, create new entries, delete entries, modify entries and more. The
+distribution also includes various parsers and generators for serializing and persisting databases.
The design of this software was influenced by the L<KeePassXC|https://github.com/keepassxreboot/keepassxc>
implementation of KeePass as well as the L<File::KeePass> module. B<File::KeePass> is an alternative module
my $kdbx = File::KDBX->load_file('mypasswords.kdbx', 'master password CHANGEME');
$kdbx->unlock; # cause $entry->password below to be defined
- $kdbx->entries->each(sub {
- my ($entry) = @_;
+ $kdbx->entries->each(sub($entry, @) {
say 'Found password for: ', $entry->title;
say ' Username: ', $entry->username;
say ' Password: ', $entry->password;
To find things in a KDBX database, you should use a filtered iterator. If you have an iterator, such as
returned by L</entries>, L</groups> or even L</objects> you can filter it using L<File::KDBX::Iterator/where>.
- my $filtered_entries = $kdbx->entries->where($query);
+ my $filtered_entries = $kdbx->entries->where(\&query);
-A C<$query> is just a subroutine that you can either write yourself or have generated for you from either
+A C<\&query> is just a subroutine that you can either write yourself or have generated for you from either
a L</"Simple Expression"> or L</"Declarative Syntax">. It's easier to have your query generated, so I'll cover
that first.
* C<==> - Number equal
* C<!=> - Number not equal
* C<< < >> - Number less than
-* C<< > >>> - Number greater than
+* C<< > >> - Number greater than
* C<< <= >> - Number less than or equal
* C<< >= >> - Number less than or equal
* C<=~> - String match regular expression
Iterators are the built-in way to navigate or walk the database tree. You get an iterator from L</entries>,
L</groups> and L</objects>. You can specify the search algorithm to iterate over objects in different orders
-using the C<algorith> option, which can be one of these L<constants|File::KDBX::Constants/":iteration">:
+using the C<algorithm> option, which can be one of these L<constants|File::KDBX::Constants/":iteration">:
=for :list
* C<ITERATION_IDS> - Iterative deepening search (default)
=head1 ERRORS
Errors in this package are constructed as L<File::KDBX::Error> objects and propagated using perl's built-in
-mechanisms. Fatal errors are propagated using L<functions/die> and non-fatal errors (a.k.a. warnings) are
-propagated using L<functions/warn> while adhering to perl's L<warnings> system. If you're already familiar
-with these mechanisms, you can skip this section.
+mechanisms. Fatal errors are propagated using L<perlfunc/"die LIST"> and non-fatal errors (a.k.a. warnings)
+are propagated using L<perlfunc/"warn LIST"> while adhering to perl's L<warnings> system. If you're already
+familiar with these mechanisms, you can skip this section.
-You can catch fatal errors using L<functions/eval> (or something like L<Try::Tiny>) and non-fatal errors using
-C<$SIG{__WARN__}> (see L<variables/%SIG>). Examples:
+You can catch fatal errors using L<perlfunc/"eval BLOCK"> (or something like L<Try::Tiny>) and non-fatal
+errors using C<$SIG{__WARN__}> (see L<perlvar/%SIG>). Examples:
use File::KDBX::Error qw(error);
* C<PERL_ONLY> - Do not use L<File::KDBX::XS> if true (default: false)
* C<NO_FORK> - Do not fork if true (default: false)
-=head1 CAVEATS
-
-Some features (e.g. parsing) require 64-bit perl. It should be possible and actually pretty easy to make it
-work using L<Math::BigInt>, but I need to build a 32-bit perl in order to test it and frankly I'm still
-figuring out how. I'm sure it's simple so I'll mark this one "TODO", but for now an exception will be thrown
-when trying to use such features with undersized IVs.
-
=head1 SEE ALSO
=for :list