]> Dogcows Code - chaz/p5-File-KDBX/blobdiff - lib/File/KDBX.pm
Fix dumping wrong-sized encryption IV
[chaz/p5-File-KDBX] / lib / File / KDBX.pm
index 8730266ab66e792bc3f6aefea6bc509567d0a7bc..d26779a03bf5493e6be3631c837cc25c53a748b3 100644 (file)
@@ -40,9 +40,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;
 }
 
@@ -238,10 +241,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 {
     +{
@@ -647,7 +652,7 @@ sub groups {
     $kdbx->add_entry($entry, %options);
     $kdbx->add_entry(%entry_attributes, %options);
 
-Add a entry to a database. This is equivalent to identifying a parent group and calling
+Add an entry to a database. This is equivalent to identifying a parent group and calling
 L<File::KDBX::Group/add_entry> on the parent group, forwarding the arguments. Available options:
 
 =for :list
@@ -1176,16 +1181,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;
 }
 
@@ -1368,7 +1381,8 @@ Remove just as many older historical entries as necessary to get under certain l
     limit: -1)
 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: value of
     L</history_max_size>, no limit: -1)
-* C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
+* C<max_age> - Maximum age (in days) of historical entries to keep (default: value of
+    L</maintenance_history_days>, no limit: -1)
 
 =cut
 
@@ -1406,13 +1420,15 @@ secure the database when dumped. The attributes that will be randomized are:
 * L</transform_seed>
 
 Randomizing these values has no effect on a loaded database. These are only used when a database is dumped.
-You normally do not need to call this method explicitly because the dumper does it explicitly by default.
+You normally do not need to call this method explicitly because the dumper does it for you by default.
 
 =cut
 
 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));
@@ -1481,7 +1497,6 @@ sub kdf {
     my %args = @_ % 2 == 1 ? (params => shift, @_) : @_;
 
     my $params = $args{params};
-    my $compat = $args{compatible} // 1;
 
     $params //= $self->kdf_parameters;
     $params = {%{$params || {}}};
@@ -1507,18 +1522,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};
 }
 
 =method cipher
@@ -1543,8 +1562,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);
@@ -1711,7 +1730,7 @@ L<File::KDBX::Loader::Raw>.
 
 =attr 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.
 
 =attr cipher_id
 
@@ -1742,7 +1761,7 @@ The transform seed I<should> be changed each time the database is saved to file.
 =attr 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.
 
 =attr encryption_iv
 
@@ -1923,14 +1942,13 @@ __END__
     );
 
     # Save the database to the filesystem
-    $kdbx->dump_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+    $kdbx->dump_file('passwords.kdbx', 'masterpw changeme');
 
     # Load the database from the filesystem into a new database instance
-    my $kdbx2 = File::KDBX->load_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+    my $kdbx2 = File::KDBX->load_file('passwords.kdbx', 'masterpw changeme');
 
     # Iterate over database entries, print entry titles
-    $kdbx2->entries->each(sub {
-        my ($entry) = @_;
+    $kdbx2->entries->each(sub($entry, @) {
         say 'Entry: ', $entry->title;
     });
 
@@ -2012,8 +2030,7 @@ across different websites. See L</SECURITY> for an overview of security consider
     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;
@@ -2080,7 +2097,7 @@ The first factor is up to you. This module does not enforce strong master keys.
 generate strong keys.
 
 The KDBX format allows for the key derivation function to be tuned. The idea is that you want each single
-brute-foce attempt to be expensive (in terms of time, CPU usage or memory usage), so that making a lot of
+brute-force attempt to be expensive (in terms of time, CPU usage or memory usage), so that making a lot of
 attempts (which would be required if you have a strong master key) gets I<really> expensive.
 
 How expensive you want to make each attempt is up to you and can depend on the application.
@@ -2215,7 +2232,7 @@ expression. For example, to search for any entry that has been used at least fiv
 
 It helps to read it right-to-left, like "usage_count is greater than or equal to 5".
 
-If you find the disambiguating structures to be distracting or confusing, you can also the
+If you find the disambiguating structures to be distracting or confusing, you can also use the
 L<File::KDBX::Util/simple_expression_query> function as a more intuitive alternative. The following example is
 equivalent to the previous:
 
@@ -2264,7 +2281,7 @@ icon:
 Note: L<File::KDBX::Constants/ICON_SMARTPHONE> is just a constant from L<File::KDBX::Constants>. It isn't
 special to this example or to queries generally. We could have just used a literal number.
 
-The important thing to notice here is how we wrapped the condition in another arrayref with a single key-value
+The important thing to notice here is how we wrapped the condition in another hashref with a single key-value
 pair where the key is the name of an operator and the value is the thing to match against. The supported
 operators are:
 
This page took 0.027165 seconds and 4 git commands to generate.