From: Charles McGarvey Date: Wed, 17 Aug 2022 01:44:59 +0000 (-0600) Subject: Version 0.906 X-Git-Url: https://git.dogcows.com/gitweb?a=commitdiff_plain;h=refs%2Fheads%2Fdist;p=chaz%2Fp5-File-KDBX Version 0.906 --- diff --git a/Changes b/Changes index 8a20613..36c8888 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,15 @@ Revision history for File-KDBX. +0.906 2022-08-16 19:44:09-0600 + * Fixed bug where dumping a fresh database could write wrong-sized encryption IV, making the resulting + serialization unreadable by some KeePass implementations. Thanks HIGHTOWE. + * Fixed bugs preventing the use of memory protection with fresh databases. Thanks HIGHTOWE. + * Fixed the transform_rounds method to work with Argon KDF; this now maps to the Argon iterations value if + the current KDF is Argon. Thanks HIGHTOWE. + 0.905 2022-08-06 12:12:42-0600 - * Declare Time::Local 1.19 as a required dependency. - * Declare CryptX 0.055 as a required dependency. Thanks HIGHTOWE. + * Declared Time::Local 1.19 as a required dependency. + * Declared CryptX 0.055 as a required dependency. Thanks HIGHTOWE. * Fixed minor documentation errors. 0.904 2022-07-07 21:51:17-0600 @@ -18,7 +25,7 @@ Revision history for File-KDBX. * Added support for 32-bit perls. * API change: Rename iterator accessors on group to all_*. * Declared perl 5.10.0 prerequisite. I have no intention of supporting 5.8 or earlier. - * Fixed more other broken tests -- thanks CPAN testers. + * Fixed more other broken tests. Thanks CPAN testers. 0.901 2022-05-02 01:18:13-0600 diff --git a/META.json b/META.json index 73423f2..296e7f1 100644 --- a/META.json +++ b/META.json @@ -176,155 +176,155 @@ "provides" : { "File::KDBX" : { "file" : "lib/File/KDBX.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Cipher" : { "file" : "lib/File/KDBX/Cipher.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Cipher::CBC" : { "file" : "lib/File/KDBX/Cipher/CBC.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Cipher::Stream" : { "file" : "lib/File/KDBX/Cipher/Stream.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Constants" : { "file" : "lib/File/KDBX/Constants.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper" : { "file" : "lib/File/KDBX/Dumper.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper::KDB" : { "file" : "lib/File/KDBX/Dumper/KDB.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper::Raw" : { "file" : "lib/File/KDBX/Dumper/Raw.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper::V3" : { "file" : "lib/File/KDBX/Dumper/V3.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper::V4" : { "file" : "lib/File/KDBX/Dumper/V4.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Dumper::XML" : { "file" : "lib/File/KDBX/Dumper/XML.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Entry" : { "file" : "lib/File/KDBX/Entry.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Error" : { "file" : "lib/File/KDBX/Error.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Group" : { "file" : "lib/File/KDBX/Group.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::IO" : { "file" : "lib/File/KDBX/IO.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::IO::Crypt" : { "file" : "lib/File/KDBX/IO/Crypt.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::IO::HashBlock" : { "file" : "lib/File/KDBX/IO/HashBlock.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::IO::HmacBlock" : { "file" : "lib/File/KDBX/IO/HmacBlock.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Iterator" : { "file" : "lib/File/KDBX/Iterator.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::KDF" : { "file" : "lib/File/KDBX/KDF.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::KDF::AES" : { "file" : "lib/File/KDBX/KDF/AES.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::KDF::Argon2" : { "file" : "lib/File/KDBX/KDF/Argon2.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key" : { "file" : "lib/File/KDBX/Key.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key::ChallengeResponse" : { "file" : "lib/File/KDBX/Key/ChallengeResponse.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key::Composite" : { "file" : "lib/File/KDBX/Key/Composite.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key::File" : { "file" : "lib/File/KDBX/Key/File.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key::Password" : { "file" : "lib/File/KDBX/Key/Password.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Key::YubiKey" : { "file" : "lib/File/KDBX/Key/YubiKey.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader" : { "file" : "lib/File/KDBX/Loader.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader::KDB" : { "file" : "lib/File/KDBX/Loader/KDB.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader::Raw" : { "file" : "lib/File/KDBX/Loader/Raw.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader::V3" : { "file" : "lib/File/KDBX/Loader/V3.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader::V4" : { "file" : "lib/File/KDBX/Loader/V4.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Loader::XML" : { "file" : "lib/File/KDBX/Loader/XML.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Object" : { "file" : "lib/File/KDBX/Object.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Safe" : { "file" : "lib/File/KDBX/Safe.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Transaction" : { "file" : "lib/File/KDBX/Transaction.pm", - "version" : "0.905" + "version" : "0.906" }, "File::KDBX::Util" : { "file" : "lib/File/KDBX/Util.pm", - "version" : "0.905" + "version" : "0.906" } }, "release_status" : "stable", @@ -339,7 +339,7 @@ "web" : "https://github.com/chazmcgarvey/File-KDBX" } }, - "version" : "0.905", + "version" : "0.906", "x_authority" : "cpan:CCM", "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.30", diff --git a/META.yml b/META.yml index 42e4c88..d9c0b21 100644 --- a/META.yml +++ b/META.yml @@ -50,118 +50,118 @@ optional_features: provides: File::KDBX: file: lib/File/KDBX.pm - version: '0.905' + version: '0.906' File::KDBX::Cipher: file: lib/File/KDBX/Cipher.pm - version: '0.905' + version: '0.906' File::KDBX::Cipher::CBC: file: lib/File/KDBX/Cipher/CBC.pm - version: '0.905' + version: '0.906' File::KDBX::Cipher::Stream: file: lib/File/KDBX/Cipher/Stream.pm - version: '0.905' + version: '0.906' File::KDBX::Constants: file: lib/File/KDBX/Constants.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper: file: lib/File/KDBX/Dumper.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper::KDB: file: lib/File/KDBX/Dumper/KDB.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper::Raw: file: lib/File/KDBX/Dumper/Raw.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper::V3: file: lib/File/KDBX/Dumper/V3.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper::V4: file: lib/File/KDBX/Dumper/V4.pm - version: '0.905' + version: '0.906' File::KDBX::Dumper::XML: file: lib/File/KDBX/Dumper/XML.pm - version: '0.905' + version: '0.906' File::KDBX::Entry: file: lib/File/KDBX/Entry.pm - version: '0.905' + version: '0.906' File::KDBX::Error: file: lib/File/KDBX/Error.pm - version: '0.905' + version: '0.906' File::KDBX::Group: file: lib/File/KDBX/Group.pm - version: '0.905' + version: '0.906' File::KDBX::IO: file: lib/File/KDBX/IO.pm - version: '0.905' + version: '0.906' File::KDBX::IO::Crypt: file: lib/File/KDBX/IO/Crypt.pm - version: '0.905' + version: '0.906' File::KDBX::IO::HashBlock: file: lib/File/KDBX/IO/HashBlock.pm - version: '0.905' + version: '0.906' File::KDBX::IO::HmacBlock: file: lib/File/KDBX/IO/HmacBlock.pm - version: '0.905' + version: '0.906' File::KDBX::Iterator: file: lib/File/KDBX/Iterator.pm - version: '0.905' + version: '0.906' File::KDBX::KDF: file: lib/File/KDBX/KDF.pm - version: '0.905' + version: '0.906' File::KDBX::KDF::AES: file: lib/File/KDBX/KDF/AES.pm - version: '0.905' + version: '0.906' File::KDBX::KDF::Argon2: file: lib/File/KDBX/KDF/Argon2.pm - version: '0.905' + version: '0.906' File::KDBX::Key: file: lib/File/KDBX/Key.pm - version: '0.905' + version: '0.906' File::KDBX::Key::ChallengeResponse: file: lib/File/KDBX/Key/ChallengeResponse.pm - version: '0.905' + version: '0.906' File::KDBX::Key::Composite: file: lib/File/KDBX/Key/Composite.pm - version: '0.905' + version: '0.906' File::KDBX::Key::File: file: lib/File/KDBX/Key/File.pm - version: '0.905' + version: '0.906' File::KDBX::Key::Password: file: lib/File/KDBX/Key/Password.pm - version: '0.905' + version: '0.906' File::KDBX::Key::YubiKey: file: lib/File/KDBX/Key/YubiKey.pm - version: '0.905' + version: '0.906' File::KDBX::Loader: file: lib/File/KDBX/Loader.pm - version: '0.905' + version: '0.906' File::KDBX::Loader::KDB: file: lib/File/KDBX/Loader/KDB.pm - version: '0.905' + version: '0.906' File::KDBX::Loader::Raw: file: lib/File/KDBX/Loader/Raw.pm - version: '0.905' + version: '0.906' File::KDBX::Loader::V3: file: lib/File/KDBX/Loader/V3.pm - version: '0.905' + version: '0.906' File::KDBX::Loader::V4: file: lib/File/KDBX/Loader/V4.pm - version: '0.905' + version: '0.906' File::KDBX::Loader::XML: file: lib/File/KDBX/Loader/XML.pm - version: '0.905' + version: '0.906' File::KDBX::Object: file: lib/File/KDBX/Object.pm - version: '0.905' + version: '0.906' File::KDBX::Safe: file: lib/File/KDBX/Safe.pm - version: '0.905' + version: '0.906' File::KDBX::Transaction: file: lib/File/KDBX/Transaction.pm - version: '0.905' + version: '0.906' File::KDBX::Util: file: lib/File/KDBX/Util.pm - version: '0.905' + version: '0.906' recommends: Compress::Raw::Zlib: '0' File::KDBX::XS: '0' @@ -215,7 +215,7 @@ resources: bugtracker: https://github.com/chazmcgarvey/File-KDBX/issues homepage: https://github.com/chazmcgarvey/File-KDBX repository: https://github.com/chazmcgarvey/File-KDBX.git -version: '0.905' +version: '0.906' x_authority: cpan:CCM x_generated_by_perl: v5.36.0 x_serialization_backend: 'YAML::Tiny version 1.73' diff --git a/Makefile.PL b/Makefile.PL index df6d119..a85a153 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -73,7 +73,7 @@ my %WriteMakefileArgs = ( "lib" => 0, "utf8" => 0 }, - "VERSION" => "0.905", + "VERSION" => "0.906", "test" => { "TESTS" => "t/*.t" } diff --git a/README b/README index 40a2050..8af7459 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ NAME VERSION - version 0.905 + version 0.906 SYNOPSIS @@ -145,7 +145,8 @@ ATTRIBUTES comment - A text string associated with the database. Often unset. + A text string associated with the database stored unencrypted in the + file header. Often unset. cipher_id @@ -181,8 +182,8 @@ ATTRIBUTES transform_rounds The number of rounds or iterations used in the key derivation function. - Increasing this number makes loading and saving the database slower by - design in order to make dictionary and brute force attacks more costly. + Increasing this number makes loading and saving the database slower in + order to make dictionary and brute force attacks more costly. encryption_iv diff --git a/lib/File/KDBX.pm b/lib/File/KDBX.pm index 2da002b..85a2a74 100644 --- a/lib/File/KDBX.pm +++ b/lib/File/KDBX.pm @@ -20,7 +20,7 @@ use Time::Piece 1.33; use boolean; use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our $WARNINGS = 1; fieldhashes \my (%SAFE, %KEYS); @@ -32,9 +32,12 @@ sub new { # copy constructor return $_[0]->clone if @_ == 1 && blessed $_[0] && $_[0]->isa($class); - my $self = bless {}, $class; + my $data; + $data = shift if is_plain_hashref($_[0]); + + my $self = bless $data // {}, $class; $self->init(@_); - $self->_set_nonlazy_attributes if empty $self; + $self->_set_nonlazy_attributes if !$data; return $self; } @@ -144,10 +147,12 @@ has raw => coerce => \&to_string; # HEADERS has 'headers.comment' => '', coerce => \&to_string; -has 'headers.cipher_id' => CIPHER_UUID_CHACHA20, coerce => \&to_uuid; +has 'headers.cipher_id' => sub { $_[0]->version < KDBX_VERSION_4_0 ? CIPHER_UUID_AES256 : CIPHER_UUID_CHACHA20 }, + coerce => \&to_uuid; has 'headers.compression_flags' => COMPRESSION_GZIP, coerce => \&to_compression_constant; has 'headers.master_seed' => sub { random_bytes(32) }, coerce => \&to_string; -has 'headers.encryption_iv' => sub { random_bytes(16) }, coerce => \&to_string; +has 'headers.encryption_iv' => sub { random_bytes($_[0]->version < KDBX_VERSION_4_0 ? 16 : 12) }, + coerce => \&to_string; has 'headers.stream_start_bytes' => sub { random_bytes(32) }, coerce => \&to_string; has 'headers.kdf_parameters' => sub { +{ @@ -753,16 +758,24 @@ sub _remove_safe { delete $SAFE{$_[0]} } sub lock { my $self = shift; - $self->_safe and return $self; - + # Find things to lock: my @strings; - $self->entries(history => 1)->each(sub { - push @strings, grep { $_->{protect} } values %{$_->strings}, values %{$_->binaries}; + my $strings = $_->strings; + for my $string_key (keys %$strings) { + my $string = $strings->{$string_key}; + push @strings, $string if $string->{protect} // $self->memory_protection($string_key); + } + push @strings, grep { $_->{protect} } values %{$_->binaries}; }); + return $self if !@strings; # nothing to do - $self->_safe(File::KDBX::Safe->new(\@strings)); - + if (my $safe = $self->_safe) { + $safe->add(\@strings); + } + else { + $self->_safe(File::KDBX::Safe->new(\@strings)); + } return $self; } @@ -892,7 +905,9 @@ sub prune_history { sub randomize_seeds { my $self = shift; - $self->encryption_iv(random_bytes(16)); + my $iv_size = 16; + $iv_size = $self->cipher(key => "\0" x 32)->iv_size if KDBX_VERSION_4_0 <= $self->version; + $self->encryption_iv(random_bytes($iv_size)); $self->inner_random_stream_key(random_bytes(64)); $self->master_seed(random_bytes(32)); $self->stream_start_bytes(random_bytes(32)); @@ -921,7 +936,6 @@ sub kdf { my %args = @_ % 2 == 1 ? (params => shift, @_) : @_; my $params = $args{params}; - my $compat = $args{compatible} // 1; $params //= $self->kdf_parameters; $params = {%{$params || {}}}; @@ -947,18 +961,22 @@ sub kdf { sub transform_seed { my $self = shift; + my $param = KDF_PARAM_AES_SEED; # Short cut: Argon2 uses the same parameter name ("S") $self->headers->{+HEADER_TRANSFORM_SEED} = - $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_SEED} = shift if @_; + $self->headers->{+HEADER_KDF_PARAMETERS}{$param} = shift if @_; $self->headers->{+HEADER_TRANSFORM_SEED} = - $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_SEED} //= random_bytes(32); + $self->headers->{+HEADER_KDF_PARAMETERS}{$param} //= random_bytes(32); } sub transform_rounds { my $self = shift; + require File::KDBX::KDF; + my $info = $File::KDBX::KDF::ROUNDS_INFO{$self->kdf_parameters->{+KDF_PARAM_UUID} // ''} // + $File::KDBX::KDF::DEFAULT_ROUNDS_INFO; $self->headers->{+HEADER_TRANSFORM_ROUNDS} = - $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_ROUNDS} = shift if @_; + $self->headers->{+HEADER_KDF_PARAMETERS}{$info->{p}} = shift if @_; $self->headers->{+HEADER_TRANSFORM_ROUNDS} = - $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_ROUNDS} //= 100_000; + $self->headers->{+HEADER_KDF_PARAMETERS}{$info->{p}} //= $info->{d}; } @@ -966,8 +984,8 @@ sub cipher { my $self = shift; my %args = @_; - $args{uuid} //= $self->headers->{+HEADER_CIPHER_ID}; - $args{iv} //= $self->headers->{+HEADER_ENCRYPTION_IV}; + $args{uuid} //= $self->cipher_id; + $args{iv} //= $self->encryption_iv; require File::KDBX::Cipher; return File::KDBX::Cipher->new(%args); @@ -1121,7 +1139,7 @@ File::KDBX - Encrypted database to store secret text and files =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS @@ -1278,7 +1296,7 @@ L. =head2 comment -A text string associated with the database. Often unset. +A text string associated with the database stored unencrypted in the file header. Often unset. =head2 cipher_id @@ -1309,7 +1327,7 @@ The transform seed I be changed each time the database is saved to file. =head2 transform_rounds The number of rounds or iterations used in the key derivation function. Increasing this number makes loading -and saving the database slower by design in order to make dictionary and brute force attacks more costly. +and saving the database slower in order to make dictionary and brute force attacks more costly. =head2 encryption_iv diff --git a/lib/File/KDBX/Cipher.pm b/lib/File/KDBX/Cipher.pm index 75ea6a8..7b0e6f3 100644 --- a/lib/File/KDBX/Cipher.pm +++ b/lib/File/KDBX/Cipher.pm @@ -12,7 +12,7 @@ use Module::Load; use Scalar::Util qw(looks_like_number); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION my %CIPHERS; @@ -158,7 +158,7 @@ File::KDBX::Cipher - A block cipher mode or cipher stream =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS @@ -304,7 +304,8 @@ This is a factory method which returns a subclass. $self->init; -Initialize the cipher. Called by . +Called by L to set attributes. You normally shouldn't call this. Returns itself to allow method +chaining. =head2 encrypt diff --git a/lib/File/KDBX/Cipher/CBC.pm b/lib/File/KDBX/Cipher/CBC.pm index 9b0e226..c5d8eab 100644 --- a/lib/File/KDBX/Cipher/CBC.pm +++ b/lib/File/KDBX/Cipher/CBC.pm @@ -11,7 +11,7 @@ use namespace::clean; extends 'File::KDBX::Cipher'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has key_size => 32; sub iv_size { 16 } @@ -63,7 +63,7 @@ File::KDBX::Cipher::CBC - A CBC block cipher mode encrypter/decrypter =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Cipher/Stream.pm b/lib/File/KDBX/Cipher/Stream.pm index 3dfa07a..fce89ad 100644 --- a/lib/File/KDBX/Cipher/Stream.pm +++ b/lib/File/KDBX/Cipher/Stream.pm @@ -14,7 +14,7 @@ use namespace::clean; extends 'File::KDBX::Cipher'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has 'counter', is => 'ro', default => 0; @@ -124,7 +124,7 @@ File::KDBX::Cipher::Stream - A cipher stream encrypter/decrypter =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Constants.pm b/lib/File/KDBX/Constants.pm index d41b351..0274597 100644 --- a/lib/File/KDBX/Constants.pm +++ b/lib/File/KDBX/Constants.pm @@ -15,7 +15,7 @@ use File::KDBX::Util qw(int64); use Scalar::Util qw(dualvar); use namespace::clean -except => 'import'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION BEGIN { my %CONSTANTS = ( @@ -348,7 +348,7 @@ File::KDBX::Constants - All the KDBX-related constants you could ever want =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Dumper.pm b/lib/File/KDBX/Dumper.pm index 421776d..7f305c7 100644 --- a/lib/File/KDBX/Dumper.pm +++ b/lib/File/KDBX/Dumper.pm @@ -15,7 +15,7 @@ use Ref::Util qw(is_ref is_scalarref); use Scalar::Util qw(looks_like_number openhandle); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub new { @@ -287,7 +287,7 @@ File::KDBX::Dumper - Write KDBX files =head1 VERSION -version 0.905 +version 0.906 =head1 ATTRIBUTES diff --git a/lib/File/KDBX/Dumper/KDB.pm b/lib/File/KDBX/Dumper/KDB.pm index d83bb0c..226902f 100644 --- a/lib/File/KDBX/Dumper/KDB.pm +++ b/lib/File/KDBX/Dumper/KDB.pm @@ -14,7 +14,7 @@ use namespace::clean; extends 'File::KDBX::Dumper'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _write_magic_numbers { '' } sub _write_headers { '' } @@ -136,7 +136,7 @@ File::KDBX::Dumper::KDB - Write KDB files =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Dumper/Raw.pm b/lib/File/KDBX/Dumper/Raw.pm index 5223def..c838b61 100644 --- a/lib/File/KDBX/Dumper/Raw.pm +++ b/lib/File/KDBX/Dumper/Raw.pm @@ -9,7 +9,7 @@ use namespace::clean; extends 'File::KDBX::Dumper'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _dump { my $self = shift; @@ -48,7 +48,7 @@ File::KDBX::Dumper::Raw - A no-op dumper that dumps content as-is =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Dumper/V3.pm b/lib/File/KDBX/Dumper/V3.pm index d5ff5d6..e769df4 100644 --- a/lib/File/KDBX/Dumper/V3.pm +++ b/lib/File/KDBX/Dumper/V3.pm @@ -16,7 +16,7 @@ use namespace::clean; extends 'File::KDBX::Dumper'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _write_headers { my $self = shift; @@ -31,6 +31,11 @@ sub _write_headers { local $headers->{+HEADER_TRANSFORM_SEED} = $kdbx->transform_seed; local $headers->{+HEADER_TRANSFORM_ROUNDS} = $kdbx->transform_rounds; + my $got_iv_size = length($headers->{+HEADER_ENCRYPTION_IV}); + alert 'Encryption IV should be exactly 16 bytes long', + got => $got_iv_size, + expected => 16 if $got_iv_size != 16; + if (nonempty (my $comment = $headers->{+HEADER_COMMENT})) { $buf .= $self->_write_header($fh, HEADER_COMMENT, $comment); } @@ -187,7 +192,7 @@ File::KDBX::Dumper::V3 - Dump KDBX3 files =head1 VERSION -version 0.905 +version 0.906 =head1 BUGS diff --git a/lib/File/KDBX/Dumper/V4.pm b/lib/File/KDBX/Dumper/V4.pm index 31f48e1..9e2fc25 100644 --- a/lib/File/KDBX/Dumper/V4.pm +++ b/lib/File/KDBX/Dumper/V4.pm @@ -19,7 +19,7 @@ use namespace::clean; extends 'File::KDBX::Dumper'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has _binaries_written => {}, is => 'ro'; @@ -238,6 +238,12 @@ sub _write_body { my $cipher = $kdbx->cipher(key => $final_key); $fh = File::KDBX::IO::Crypt->new($fh, cipher => $cipher); + my $got_iv_size = length($kdbx->headers->{+HEADER_ENCRYPTION_IV}); + my $iv_size = $cipher->iv_size; + alert "Encryption IV should be $iv_size bytes long", + got => $got_iv_size, + expected => $iv_size if $got_iv_size != $iv_size; + my $compress = $kdbx->headers->{+HEADER_COMPRESSION_FLAGS}; if ($compress == COMPRESSION_GZIP) { load_optional('IO::Compress::Gzip'); @@ -374,7 +380,7 @@ File::KDBX::Dumper::V4 - Dump KDBX4 files =head1 VERSION -version 0.905 +version 0.906 =head1 BUGS diff --git a/lib/File/KDBX/Dumper/XML.pm b/lib/File/KDBX/Dumper/XML.pm index b5cd47c..71b9999 100644 --- a/lib/File/KDBX/Dumper/XML.pm +++ b/lib/File/KDBX/Dumper/XML.pm @@ -19,7 +19,7 @@ use namespace::clean; extends 'File::KDBX::Dumper'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has allow_protection => 1; @@ -563,7 +563,7 @@ File::KDBX::Dumper::XML - Dump unencrypted XML KeePass files =head1 VERSION -version 0.905 +version 0.906 =head1 ATTRIBUTES diff --git a/lib/File/KDBX/Entry.pm b/lib/File/KDBX/Entry.pm index 6ca2051..76f2318 100644 --- a/lib/File/KDBX/Entry.pm +++ b/lib/File/KDBX/Entry.pm @@ -21,7 +21,7 @@ use namespace::clean; extends 'File::KDBX::Object'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION my $PLACEHOLDER_MAX_DEPTH = 10; my %PLACEHOLDERS; @@ -129,14 +129,16 @@ sub string { return $self->{strings}{$key} = $args{value} if is_plain_hashref($args{value}); + # Auto-vivify the standard strings. + if (!exists $self->{strings}{$key} && $STANDARD_STRINGS{$key}) { + $args{value} //= ''; + $args{protect} //= true if $self->_protect($key); + } + while (my ($field, $value) = each %args) { $self->{strings}{$key}{$field} = $value; } - # Auto-vivify the standard strings. - if ($STANDARD_STRINGS{$key}) { - return $self->{strings}{$key} //= {value => '', $self->_protect($key) ? (protect => true) : ()}; - } return $self->{strings}{$key}; } @@ -681,7 +683,7 @@ File::KDBX::Entry - A KDBX database entry =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Error.pm b/lib/File/KDBX/Error.pm index f041968..f64cb71 100644 --- a/lib/File/KDBX/Error.pm +++ b/lib/File/KDBX/Error.pm @@ -9,7 +9,7 @@ use Exporter qw(import); use Scalar::Util qw(blessed looks_like_number); use namespace::clean -except => 'import'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our @EXPORT = qw(alert error throw); @@ -167,7 +167,7 @@ File::KDBX::Error - Represents something bad that happened =head1 VERSION -version 0.905 +version 0.906 =head1 ATTRIBUTES diff --git a/lib/File/KDBX/Group.pm b/lib/File/KDBX/Group.pm index 1e7e1c5..531210c 100644 --- a/lib/File/KDBX/Group.pm +++ b/lib/File/KDBX/Group.pm @@ -19,7 +19,7 @@ use namespace::clean; extends 'File::KDBX::Object'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION # has uuid => sub { generate_uuid(printable => 1) }; @@ -398,7 +398,7 @@ File::KDBX::Group - A KDBX database group =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/IO.pm b/lib/File/KDBX/IO.pm index cffcf88..54ce5a7 100644 --- a/lib/File/KDBX/IO.pm +++ b/lib/File/KDBX/IO.pm @@ -14,7 +14,7 @@ use namespace::clean; extends 'IO::Handle'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _croak { require Carp; goto &Carp::croak } @@ -390,7 +390,7 @@ File::KDBX::IO - Base IO class for KDBX-related streams =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/IO/Crypt.pm b/lib/File/KDBX/IO/Crypt.pm index 3b2636f..e2fd8f7 100644 --- a/lib/File/KDBX/IO/Crypt.pm +++ b/lib/File/KDBX/IO/Crypt.pm @@ -11,7 +11,7 @@ use namespace::clean; extends 'File::KDBX::IO'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our $BUFFER_SIZE = 16384; our $ERROR; @@ -139,7 +139,7 @@ File::KDBX::IO::Crypt - Encrypter/decrypter IO handle =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/IO/HashBlock.pm b/lib/File/KDBX/IO/HashBlock.pm index ae8aac1..9b6764d 100644 --- a/lib/File/KDBX/IO/HashBlock.pm +++ b/lib/File/KDBX/IO/HashBlock.pm @@ -13,7 +13,7 @@ use namespace::clean; extends 'File::KDBX::IO'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our $ALGORITHM = 'SHA256'; our $BLOCK_SIZE = 1048576; # 1MiB our $ERROR; @@ -208,7 +208,7 @@ File::KDBX::IO::HashBlock - Hash block stream IO handle =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/IO/HmacBlock.pm b/lib/File/KDBX/IO/HmacBlock.pm index 69afacd..3339542 100644 --- a/lib/File/KDBX/IO/HmacBlock.pm +++ b/lib/File/KDBX/IO/HmacBlock.pm @@ -13,7 +13,7 @@ use namespace::clean; extends 'File::KDBX::IO'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our $BLOCK_SIZE = 1048576; # 1MiB our $ERROR; @@ -212,7 +212,7 @@ File::KDBX::IO::HmacBlock - HMAC block stream IO handle =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Iterator.pm b/lib/File/KDBX/Iterator.pm index 6b9a153..de19762 100644 --- a/lib/File/KDBX/Iterator.pm +++ b/lib/File/KDBX/Iterator.pm @@ -14,7 +14,7 @@ use namespace::clean; BEGIN { mark_as_loaded('Iterator::Simple::Iterator') } extends 'Iterator::Simple::Iterator'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub new { @@ -221,7 +221,7 @@ File::KDBX::Iterator - KDBX database iterator =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/KDF.pm b/lib/File/KDBX/KDF.pm index 91d9bac..41407bd 100644 --- a/lib/File/KDBX/KDF.pm +++ b/lib/File/KDBX/KDF.pm @@ -12,10 +12,19 @@ use Module::Load; use Scalar::Util qw(blessed); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION my %KDFS; +our %ROUNDS_INFO = ( + KDF_UUID_ARGON2D() => {p => KDF_PARAM_ARGON2_ITERATIONS, d => KDF_DEFAULT_ARGON2_ITERATIONS}, + KDF_UUID_ARGON2ID() => {p => KDF_PARAM_ARGON2_ITERATIONS, d => KDF_DEFAULT_ARGON2_ITERATIONS}, +); +our $DEFAULT_ROUNDS_INFO = { + p => KDF_PARAM_AES_ROUNDS, + d => KDF_DEFAULT_AES_ROUNDS, +}; + sub new { my $class = shift; @@ -120,7 +129,7 @@ File::KDBX::KDF - A key derivation function =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION @@ -194,7 +203,8 @@ Construct a new KDF. $kdf = $kdf->init(%attributes); -Called by method to set attributes. You normally shouldn't call this. +Called by L to set attributes. You normally shouldn't call this. Returns itself to allow method +chaining. =head2 transform @@ -213,7 +223,7 @@ so challenge-response keys can produce raw keys. See L. $kdf->randomize_seed; -Generate a new random seed/salt. +Generate and set a new random seed/salt. =head2 register diff --git a/lib/File/KDBX/KDF/AES.pm b/lib/File/KDBX/KDF/AES.pm index 05d707f..41863dd 100644 --- a/lib/File/KDBX/KDF/AES.pm +++ b/lib/File/KDBX/KDF/AES.pm @@ -13,7 +13,7 @@ use namespace::clean; extends 'File::KDBX::KDF'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION # Rounds higher than this are eligible for forking: my $FORK_OPTIMIZATION_THRESHOLD = 100_000; @@ -111,7 +111,7 @@ File::KDBX::KDF::AES - Using the AES cipher as a key derivation function =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/KDF/Argon2.pm b/lib/File/KDBX/KDF/Argon2.pm index e1c8b44..30514e2 100644 --- a/lib/File/KDBX/KDF/Argon2.pm +++ b/lib/File/KDBX/KDF/Argon2.pm @@ -12,7 +12,7 @@ use namespace::clean; extends 'File::KDBX::KDF'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub salt { $_[0]->{+KDF_PARAM_ARGON2_SALT} or throw 'Salt is not set' } @@ -69,7 +69,7 @@ File::KDBX::KDF::Argon2 - The Argon2 family of key derivation functions =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Key.pm b/lib/File/KDBX/Key.pm index 7e9a17e..478d615 100644 --- a/lib/File/KDBX/Key.pm +++ b/lib/File/KDBX/Key.pm @@ -14,7 +14,7 @@ use Ref::Util qw(is_arrayref is_coderef is_hashref is_ref is_scalarref); use Scalar::Util qw(blessed openhandle); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION fieldhashes \my %SAFE; @@ -140,7 +140,7 @@ File::KDBX::Key - A credential that can protect a KDBX file =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Key/ChallengeResponse.pm b/lib/File/KDBX/Key/ChallengeResponse.pm index e9c47fa..dc220dc 100644 --- a/lib/File/KDBX/Key/ChallengeResponse.pm +++ b/lib/File/KDBX/Key/ChallengeResponse.pm @@ -10,7 +10,7 @@ use namespace::clean; extends 'File::KDBX::Key'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub init { my $self = shift; @@ -56,7 +56,7 @@ File::KDBX::Key::ChallengeResponse - A challenge-response key =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Key/Composite.pm b/lib/File/KDBX/Key/Composite.pm index 8d87370..0aaa261 100644 --- a/lib/File/KDBX/Key/Composite.pm +++ b/lib/File/KDBX/Key/Composite.pm @@ -13,7 +13,7 @@ use namespace::clean; extends 'File::KDBX::Key'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub init { my $self = shift; @@ -92,7 +92,7 @@ File::KDBX::Key::Composite - A composite key made up of component keys =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Key/File.pm b/lib/File/KDBX/Key/File.pm index 55dbb26..ad45f53 100644 --- a/lib/File/KDBX/Key/File.pm +++ b/lib/File/KDBX/Key/File.pm @@ -17,7 +17,7 @@ use namespace::clean; extends 'File::KDBX::Key'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has 'type', is => 'ro'; @@ -262,7 +262,7 @@ File::KDBX::Key::File - A file key =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Key/Password.pm b/lib/File/KDBX/Key/Password.pm index 1a899fc..4aa7eeb 100644 --- a/lib/File/KDBX/Key/Password.pm +++ b/lib/File/KDBX/Key/Password.pm @@ -12,7 +12,7 @@ use namespace::clean; extends 'File::KDBX::Key'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub init { my $self = shift; @@ -37,7 +37,7 @@ File::KDBX::Key::Password - A password key =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Key/YubiKey.pm b/lib/File/KDBX/Key/YubiKey.pm index 515b23c..1b7d0df 100644 --- a/lib/File/KDBX/Key/YubiKey.pm +++ b/lib/File/KDBX/Key/YubiKey.pm @@ -14,7 +14,7 @@ use namespace::clean; extends 'File::KDBX::Key::ChallengeResponse'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION # It can take some time for the USB device to be ready again, so we can retry a few times. our $RETRY_COUNT = 5; @@ -296,7 +296,7 @@ File::KDBX::Key::YubiKey - A Yubico challenge-response key =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Loader.pm b/lib/File/KDBX/Loader.pm index 1be16a5..0ff3933 100644 --- a/lib/File/KDBX/Loader.pm +++ b/lib/File/KDBX/Loader.pm @@ -14,7 +14,7 @@ use Ref::Util qw(is_ref is_scalarref); use Scalar::Util qw(looks_like_number openhandle); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub new { @@ -249,7 +249,7 @@ File::KDBX::Loader - Load KDBX files =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Loader/KDB.pm b/lib/File/KDBX/Loader/KDB.pm index b4f6620..0635ce4 100644 --- a/lib/File/KDBX/Loader/KDB.pm +++ b/lib/File/KDBX/Loader/KDB.pm @@ -17,7 +17,7 @@ use namespace::clean; extends 'File::KDBX::Loader'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION my $DEFAULT_EXPIRATION = Time::Piece->strptime('2999-12-31 23:59:59', '%Y-%m-%d %H:%M:%S'); @@ -397,7 +397,7 @@ File::KDBX::Loader::KDB - Read KDB files =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Loader/Raw.pm b/lib/File/KDBX/Loader/Raw.pm index c7921d1..15ebb54 100644 --- a/lib/File/KDBX/Loader/Raw.pm +++ b/lib/File/KDBX/Loader/Raw.pm @@ -9,7 +9,7 @@ use namespace::clean; extends 'File::KDBX::Loader'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _read { my $self = shift; @@ -47,7 +47,7 @@ File::KDBX::Loader::Raw - A no-op loader that doesn't do any parsing =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Loader/V3.pm b/lib/File/KDBX/Loader/V3.pm index 0de336f..865ae5a 100644 --- a/lib/File/KDBX/Loader/V3.pm +++ b/lib/File/KDBX/Loader/V3.pm @@ -27,7 +27,7 @@ use namespace::clean; extends 'File::KDBX::Loader'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _read_header { my $self = shift; @@ -174,7 +174,7 @@ File::KDBX::Loader::V3 - Load KDBX3 files =head1 VERSION -version 0.905 +version 0.906 =head1 BUGS diff --git a/lib/File/KDBX/Loader/V4.pm b/lib/File/KDBX/Loader/V4.pm index 1ef3c82..882ac1b 100644 --- a/lib/File/KDBX/Loader/V4.pm +++ b/lib/File/KDBX/Loader/V4.pm @@ -30,7 +30,7 @@ use namespace::clean; extends 'File::KDBX::Loader'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub _read_header { my $self = shift; @@ -273,7 +273,7 @@ File::KDBX::Loader::V4 - Load KDBX4 files =head1 VERSION -version 0.905 +version 0.906 =head1 BUGS diff --git a/lib/File/KDBX/Loader/XML.pm b/lib/File/KDBX/Loader/XML.pm index 3736ba1..81d548f 100644 --- a/lib/File/KDBX/Loader/XML.pm +++ b/lib/File/KDBX/Loader/XML.pm @@ -18,7 +18,7 @@ use namespace::clean; extends 'File::KDBX::Loader'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION has '_reader', is => 'ro'; has '_safe', is => 'ro', default => sub { File::KDBX::Safe->new(cipher => $_[0]->kdbx->random_stream) }; @@ -589,7 +589,7 @@ File::KDBX::Loader::XML - Load unencrypted XML KeePass files =head1 VERSION -version 0.905 +version 0.906 =head1 BUGS diff --git a/lib/File/KDBX/Object.pm b/lib/File/KDBX/Object.pm index 39c3eb1..38fa1e4 100644 --- a/lib/File/KDBX/Object.pm +++ b/lib/File/KDBX/Object.pm @@ -14,7 +14,7 @@ use Ref::Util qw(is_arrayref is_plain_arrayref is_plain_hashref is_ref); use Scalar::Util qw(blessed weaken); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION fieldhashes \my (%KDBX, %PARENT, %TXNS, %REFS, %SIGNALS); @@ -391,9 +391,9 @@ sub _txns { $TXNS{$_[0]} //= [] } sub _commit { die 'Not implemented' } # Get a reference to an object that represents an object's committed state. If there is no pending -# transaction, this is just $self. If there is a transaction, this is the snapshot take before the transaction -# began. This method is private because it provides direct access to the actual snapshot. It is important that -# the snapshot not be changed or a rollback would roll back to an altered state. +# transaction, this is just $self. If there is a transaction, this is the snapshot taken immediately before +# the transaction began. This method is private because it provides direct access to the actual snapshot. It +# is important that the snapshot not be changed or a rollback would roll back to an altered state. # This is used by File::KDBX::Dumper::XML so as to not dump uncommitted changes. sub _committed { my $self = shift; @@ -526,7 +526,7 @@ File::KDBX::Object - A KDBX database object =head1 VERSION -version 0.905 +version 0.906 =head1 DESCRIPTION diff --git a/lib/File/KDBX/Safe.pm b/lib/File/KDBX/Safe.pm index b56ffc2..e8c4f94 100644 --- a/lib/File/KDBX/Safe.pm +++ b/lib/File/KDBX/Safe.pm @@ -14,7 +14,7 @@ use Ref::Util qw(is_arrayref is_coderef is_hashref is_scalarref); use Scalar::Util qw(refaddr); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub new { @@ -217,7 +217,7 @@ File::KDBX::Safe - Keep strings encrypted while in memory =head1 VERSION -version 0.905 +version 0.906 =head1 SYNOPSIS diff --git a/lib/File/KDBX/Transaction.pm b/lib/File/KDBX/Transaction.pm index 1c0cb4f..6a4769c 100644 --- a/lib/File/KDBX/Transaction.pm +++ b/lib/File/KDBX/Transaction.pm @@ -8,7 +8,7 @@ use Devel::GlobalDestruction; use File::KDBX::Util qw(:class); use namespace::clean; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION sub new { @@ -59,7 +59,7 @@ File::KDBX::Transaction - Make multiple database edits atomically =head1 VERSION -version 0.905 +version 0.906 =head1 ATTRIBUTES diff --git a/lib/File/KDBX/Util.pm b/lib/File/KDBX/Util.pm index d0e4673..af04f15 100644 --- a/lib/File/KDBX/Util.pm +++ b/lib/File/KDBX/Util.pm @@ -17,7 +17,7 @@ use Time::Piece 1.33; use boolean; use namespace::clean -except => 'import'; -our $VERSION = '0.905'; # VERSION +our $VERSION = '0.906'; # VERSION our %EXPORT_TAGS = ( assert => [qw(DEBUG assert)], @@ -870,7 +870,7 @@ File::KDBX::Util - Utility functions for working with KDBX files =head1 VERSION -version 0.905 +version 0.906 =head1 FUNCTIONS diff --git a/t/database.t b/t/database.t index 997d04c..8b26f6a 100644 --- a/t/database.t +++ b/t/database.t @@ -8,6 +8,7 @@ use FindBin qw($Bin); use lib "$Bin/lib"; use TestCommon; +use File::KDBX::Constants qw(:cipher :version); use File::KDBX; use File::Temp qw(tempfile); use Test::Deep; @@ -29,6 +30,14 @@ subtest 'Create a new database' => sub { $entry->remove; ok $kdbx->_has_implicit_root, 'Removing group makes the root group implicit again'; + + cmp_ok $kdbx->version, '==', KDBX_VERSION_3_1, 'Default KDBX file version is 3.1'; + is $kdbx->cipher_id, CIPHER_UUID_AES256, 'Cipher of new database is AES256'; + cmp_ok length($kdbx->encryption_iv), '==', 16, 'Encryption IV of new databse is 16 bytes'; + + my $kdbx2 = File::KDBX->new(version => KDBX_VERSION_4_0); + is $kdbx2->cipher_id, CIPHER_UUID_CHACHA20, 'Cipher of new v4 database is ChaCha20'; + cmp_ok length($kdbx2->encryption_iv), '==', 12, 'Encryption IV of new databse is 12 bytes'; }; subtest 'Clone' => sub { diff --git a/t/entry.t b/t/entry.t index 1581608..a5700c9 100644 --- a/t/entry.t +++ b/t/entry.t @@ -84,6 +84,17 @@ subtest 'Accessors' => sub { $entry->creation_time('2022-02-02 12:34:56'); cmp_ok $entry->creation_time->epoch, '==', 1643805296, 'Creation time coerced into a Time::Piece (epoch)'; is $entry->creation_time->datetime, '2022-02-02T12:34:56', 'Creation time coerced into a Time::Piece'; + + $entry->username('foo'); + cmp_deeply $entry->strings->{UserName}, { + value => 'foo', + }, 'Username setter works'; + + $entry->password('bar'); + cmp_deeply $entry->strings->{Password}, { + value => 'bar', + protect => bool(1), + }, 'Password setter works'; }; subtest 'Custom icons' => sub { @@ -169,4 +180,32 @@ subtest 'Auto-type' => sub { is $keys, 'blah', 'Select the correct association'; }; +subtest 'Memory protection' => sub { + my $kdbx = File::KDBX->new; + + is exception { $kdbx->lock }, undef, 'Can lock empty database'; + $kdbx->unlock; # should be no-op since nothing was locked + + my $entry = $kdbx->root->add_entry( + title => 'My Bank', + username => 'mreynolds', + password => 's3cr3t', + ); + $entry->string(Custom => 'foo', protect => 1); + $entry->binary(Binary => 'bar', protect => 1); + $entry->binary(UnprotectedBinary => 'baz'); + + is exception { $kdbx->lock }, undef, 'Can lock new database'; + is $entry->username, 'mreynolds', 'UserName does not get locked'; + is $entry->password, undef, 'Password is lockable'; + is $entry->string_value('Custom'), undef, 'Custom is lockable'; + is $entry->binary_value('Binary'), undef, 'Binary is lockable'; + is $entry->binary_value('UnprotectedBinary'), 'baz', 'Unprotected binary does not get locked'; + + $kdbx->unlock; + is $entry->password, 's3cr3t', 'Password is unlockable'; + is $entry->string_value('Custom'), 'foo', 'Custom is unlockable'; + is $entry->binary_value('Binary'), 'bar', 'Binary is unlockable'; +}; + done_testing; diff --git a/t/kdbx4.t b/t/kdbx4.t index ff48700..b14ddcf 100644 --- a/t/kdbx4.t +++ b/t/kdbx4.t @@ -34,6 +34,11 @@ subtest 'Verify Format400' => sub { master_seed => ";\372y\300yS%\3331\177\231\364u\265Y\361\225\3273h\332R,\22\240a\240\302\271\357\313\23", }, 'Extract headers' or diag explain $kdbx->headers; + is $kdbx->transform_seed, + "V\254\6m-\206*\260\305\f\0\366\24:4\235\364A\362\346\221\13)}\250\217P\303\303\2\331\245", + 'Get the correct transform seed'; + cmp_ok $kdbx->transform_rounds, '==', 2, 'Get the correct transform rounds'; + is $kdbx->meta->{database_name}, 'Format400', 'Extract database name from meta'; is $kdbx->root->name, 'Format400', 'Extract name of root group'; @@ -132,10 +137,10 @@ sub test_upgrade_master_key_integrity { plan tests => $expected_version >= KDBX_VERSION_4_0 ? 6 : 5; my $kdbx = File::KDBX->new; - $kdbx->kdf_parameters(fast_kdf); - is $kdbx->kdf->uuid, KDF_UUID_AES, 'Default KDF is AES'; + $kdbx->kdf_parameters(fast_kdf); + { local $_ = $kdbx; $modifier->($kdbx); @@ -216,4 +221,28 @@ subtest 'Custom data' => sub { is_deeply $entry2->custom_data_value('bool'), '0', 'Store a boolean in entry custom data'; }; +subtest 'KDF parameters' => sub { + my $kdbx = File::KDBX->new; + $kdbx->version(KDBX_VERSION_4_0); + + is $kdbx->kdf_parameters->{+KDF_PARAM_UUID}, KDF_UUID_AES, 'Default KDF type is correct'; + cmp_ok $kdbx->transform_rounds, '==', 100_000, 'Default transform rounds is correct'; + + $kdbx->transform_rounds(17); + cmp_deeply $kdbx->kdf_parameters, { + "\$UUID" => "\311\331\363\232b\212D`\277t\r\b\301\212O\352", + R => num(17), + S => ignore(), + }, 'Set transform rounds for AES KDF'; + + $kdbx->kdf_parameters({KDF_PARAM_UUID() => KDF_UUID_ARGON2D}); + cmp_ok $kdbx->transform_rounds, '==', 10, 'Default Argon2D transform rounds is correct'; + + $kdbx->transform_rounds(17); + cmp_deeply $kdbx->kdf_parameters, { + "\$UUID" => "\357cm\337\214)DK\221\367\251\244\3\343\n\f", + I => num(17), + }, 'Set transform rounds for Argon KDF'; +}; + done_testing; diff --git a/t/lib/TestCommon.pm b/t/lib/TestCommon.pm index 33438d3..195b76c 100644 --- a/t/lib/TestCommon.pm +++ b/t/lib/TestCommon.pm @@ -80,6 +80,7 @@ sub ok_magic { ], $note // 'KDBX magic numbers are correct'; } +# Returns parameters for a fast KDF so that running tests isn't pointlessly slow. sub fast_kdf { my $uuid = shift // KDF_UUID_AES; my $params = {