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
* 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
"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",
"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",
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'
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'
"lib" => 0,
"utf8" => 0
},
- "VERSION" => "0.905",
+ "VERSION" => "0.906",
"test" => {
"TESTS" => "t/*.t"
}
VERSION
- version 0.905
+ version 0.906
SYNOPSIS
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
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
use boolean;
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
our $WARNINGS = 1;
fieldhashes \my (%SAFE, %KEYS);
# 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;
}
# 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 {
+{
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;
}
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));
my %args = @_ % 2 == 1 ? (params => shift, @_) : @_;
my $params = $args{params};
- my $compat = $args{compatible} // 1;
$params //= $self->kdf_parameters;
$params = {%{$params || {}}};
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};
}
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);
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
=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
=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
use Scalar::Util qw(looks_like_number);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
my %CIPHERS;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
$self->init;
-Initialize the cipher. Called by </new>.
+Called by L</new> to set attributes. You normally shouldn't call this. Returns itself to allow method
+chaining.
=head2 encrypt
extends 'File::KDBX::Cipher';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
has key_size => 32;
sub iv_size { 16 }
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Cipher';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
has 'counter', is => 'ro', default => 0;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
use Scalar::Util qw(dualvar);
use namespace::clean -except => 'import';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
BEGIN {
my %CONSTANTS = (
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
use Scalar::Util qw(looks_like_number openhandle);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub new {
=head1 VERSION
-version 0.905
+version 0.906
=head1 ATTRIBUTES
extends 'File::KDBX::Dumper';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _write_magic_numbers { '' }
sub _write_headers { '' }
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'File::KDBX::Dumper';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _dump {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _write_headers {
my $self = shift;
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);
}
=head1 VERSION
-version 0.905
+version 0.906
=head1 BUGS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
has _binaries_written => {}, is => 'ro';
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');
=head1 VERSION
-version 0.905
+version 0.906
=head1 BUGS
extends 'File::KDBX::Dumper';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
has allow_protection => 1;
=head1 VERSION
-version 0.905
+version 0.906
=head1 ATTRIBUTES
extends 'File::KDBX::Object';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
my $PLACEHOLDER_MAX_DEPTH = 10;
my %PLACEHOLDERS;
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};
}
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
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);
=head1 VERSION
-version 0.905
+version 0.906
=head1 ATTRIBUTES
extends 'File::KDBX::Object';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
# has uuid => sub { generate_uuid(printable => 1) };
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'IO::Handle';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _croak { require Carp; goto &Carp::croak }
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'File::KDBX::IO';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
our $BUFFER_SIZE = 16384;
our $ERROR;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::IO';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
our $ALGORITHM = 'SHA256';
our $BLOCK_SIZE = 1048576; # 1MiB
our $ERROR;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'File::KDBX::IO';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
our $BLOCK_SIZE = 1048576; # 1MiB
our $ERROR;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
BEGIN { mark_as_loaded('Iterator::Simple::Iterator') }
extends 'Iterator::Simple::Iterator';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub new {
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
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;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
$kdf = $kdf->init(%attributes);
-Called by method to set attributes. You normally shouldn't call this.
+Called by L</new> to set attributes. You normally shouldn't call this. Returns itself to allow method
+chaining.
=head2 transform
$kdf->randomize_seed;
-Generate a new random seed/salt.
+Generate and set a new random seed/salt.
=head2 register
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;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
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' }
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
use Scalar::Util qw(blessed openhandle);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
fieldhashes \my %SAFE;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'File::KDBX::Key';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Key';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Key';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
has 'type', is => 'ro';
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Key';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub init {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
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;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
use Scalar::Util qw(looks_like_number openhandle);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub new {
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
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');
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
extends 'File::KDBX::Loader';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _read {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
extends 'File::KDBX::Loader';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _read_header {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 BUGS
extends 'File::KDBX::Loader';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub _read_header {
my $self = shift;
=head1 VERSION
-version 0.905
+version 0.906
=head1 BUGS
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) };
=head1 VERSION
-version 0.905
+version 0.906
=head1 BUGS
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);
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;
=head1 VERSION
-version 0.905
+version 0.906
=head1 DESCRIPTION
use Scalar::Util qw(refaddr);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub new {
=head1 VERSION
-version 0.905
+version 0.906
=head1 SYNOPSIS
use File::KDBX::Util qw(:class);
use namespace::clean;
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
sub new {
=head1 VERSION
-version 0.905
+version 0.906
=head1 ATTRIBUTES
use boolean;
use namespace::clean -except => 'import';
-our $VERSION = '0.905'; # VERSION
+our $VERSION = '0.906'; # VERSION
our %EXPORT_TAGS = (
assert => [qw(DEBUG assert)],
=head1 VERSION
-version 0.905
+version 0.906
=head1 FUNCTIONS
use lib "$Bin/lib";
use TestCommon;
+use File::KDBX::Constants qw(:cipher :version);
use File::KDBX;
use File::Temp qw(tempfile);
use Test::Deep;
$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 {
$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 {
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;
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';
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);
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;
], $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 = {