X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=lib%2FFile%2FKDBX.pm;h=2e7c1e505fd7346774b0dd56fa427f7bbedab688;hb=4dc2a1996dfcf2dfda3c554daa2f5f59fa763494;hp=2326b9b5d403f9ff2b631cc9e20c3b5c9c1ae466;hpb=05e0bcef1c2165c556b910314312866dc4a667b7;p=chaz%2Fp5-File-KDBX diff --git a/lib/File/KDBX.pm b/lib/File/KDBX.pm index 2326b9b..2e7c1e5 100644 --- a/lib/File/KDBX.pm +++ b/lib/File/KDBX.pm @@ -9,7 +9,7 @@ use Devel::GlobalDestruction; use File::KDBX::Constants qw(:all); use File::KDBX::Error; use File::KDBX::Safe; -use File::KDBX::Util qw(:empty :uuid :search erase simple_expression_query snakify); +use File::KDBX::Util qw(:class :coercion :empty :uuid :search erase simple_expression_query snakify); use Hash::Util::FieldHash qw(fieldhashes); use List::Util qw(any); use Ref::Util qw(is_ref is_arrayref is_plain_hashref); @@ -40,7 +40,7 @@ sub new { my $self = bless {}, $class; $self->init(@_); - $self->_set_default_attributes if empty $self; + $self->_set_nonlazy_attributes if empty $self; return $self; } @@ -120,6 +120,9 @@ sub STORABLE_thaw { $KEYS{$self} = $key; $SAFE{$self} = $safe; + # Dualvars aren't cloned as dualvars, so coerce the compression flags. + $self->compression_flags($self->compression_flags); + for my $object (@{$self->all_groups}, @{$self->all_entries(history => 1)}) { $object->kdbx($self); } @@ -223,118 +226,82 @@ sub user_agent_string { __PACKAGE__, $VERSION, @Config::Config{qw(package version osname osvers archname)}); } -my %ATTRS = ( - sig1 => KDBX_SIG1, - sig2 => KDBX_SIG2_2, - version => KDBX_VERSION_3_1, - headers => sub { +{} }, - inner_headers => sub { +{} }, - meta => sub { +{} }, - binaries => sub { +{} }, - deleted_objects => sub { +{} }, - raw => undef, -); -my %ATTRS_HEADERS = ( - HEADER_COMMENT() => '', - HEADER_CIPHER_ID() => CIPHER_UUID_CHACHA20, - HEADER_COMPRESSION_FLAGS() => COMPRESSION_GZIP, - HEADER_MASTER_SEED() => sub { random_bytes(32) }, - # HEADER_TRANSFORM_SEED() => sub { random_bytes(32) }, - # HEADER_TRANSFORM_ROUNDS() => 100_000, - HEADER_ENCRYPTION_IV() => sub { random_bytes(16) }, - # HEADER_INNER_RANDOM_STREAM_KEY() => sub { random_bytes(32) }, # 64? - HEADER_STREAM_START_BYTES() => sub { random_bytes(32) }, - # HEADER_INNER_RANDOM_STREAM_ID() => STREAM_ID_CHACHA20, - HEADER_KDF_PARAMETERS() => sub { - +{ - KDF_PARAM_UUID() => KDF_UUID_AES, - KDF_PARAM_AES_ROUNDS() => $_[0]->headers->{+HEADER_TRANSFORM_ROUNDS} // KDF_DEFAULT_AES_ROUNDS, - KDF_PARAM_AES_SEED() => $_[0]->headers->{+HEADER_TRANSFORM_SEED} // random_bytes(32), - }; - }, - # HEADER_PUBLIC_CUSTOM_DATA() => sub { +{} }, -); -my %ATTRS_META = ( - generator => '', - header_hash => '', - database_name => '', - database_name_changed => sub { scalar gmtime }, - database_description => '', - database_description_changed => sub { scalar gmtime }, - default_username => '', - default_username_changed => sub { scalar gmtime }, - maintenance_history_days => 0, - color => '', - master_key_changed => sub { scalar gmtime }, - master_key_change_rec => -1, - master_key_change_force => -1, - # memory_protection => sub { +{} }, - custom_icons => sub { +{} }, - recycle_bin_enabled => true, - recycle_bin_uuid => "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", - recycle_bin_changed => sub { scalar gmtime }, - entry_templates_group => "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", - entry_templates_group_changed => sub { scalar gmtime }, - last_selected_group => "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", - last_top_visible_group => "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", - history_max_items => HISTORY_DEFAULT_MAX_ITEMS, - history_max_size => HISTORY_DEFAULT_MAX_SIZE, - settings_changed => sub { scalar gmtime }, - # binaries => sub { +{} }, - # custom_data => sub { +{} }, -); -my %ATTRS_MEMORY_PROTECTION = ( - protect_title => false, - protect_username => false, - protect_password => true, - protect_url => false, - protect_notes => false, - # auto_enable_visual_hiding => false, -); - -while (my ($attr, $default) = each %ATTRS) { - no strict 'refs'; ## no critic (ProhibitNoStrict) - *{$attr} = sub { - my $self = shift; - $self->{$attr} = shift if @_; - $self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default; - }; -} -while (my ($attr, $default) = each %ATTRS_HEADERS) { - no strict 'refs'; ## no critic (ProhibitNoStrict) - *{$attr} = sub { - my $self = shift; - $self->headers->{$attr} = shift if @_; - $self->headers->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default; - }; -} -while (my ($attr, $default) = each %ATTRS_META) { - no strict 'refs'; ## no critic (ProhibitNoStrict) - *{$attr} = sub { - my $self = shift; - $self->meta->{$attr} = shift if @_; - $self->meta->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default; +has sig1 => KDBX_SIG1, coerce => \&to_number; +has sig2 => KDBX_SIG2_2, coerce => \&to_number; +has version => KDBX_VERSION_3_1, coerce => \&to_number; +has headers => {}; +has inner_headers => {}; +has meta => {}; +has binaries => {}; +has deleted_objects => {}; +has raw => coerce => \&to_string; + +# HEADERS +has 'headers.comment' => '', coerce => \&to_string; +has 'headers.cipher_id' => 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.stream_start_bytes' => sub { random_bytes(32) }, coerce => \&to_string; +has 'headers.kdf_parameters' => sub { + +{ + KDF_PARAM_UUID() => KDF_UUID_AES, + KDF_PARAM_AES_ROUNDS() => $_[0]->headers->{+HEADER_TRANSFORM_ROUNDS} // KDF_DEFAULT_AES_ROUNDS, + KDF_PARAM_AES_SEED() => $_[0]->headers->{+HEADER_TRANSFORM_SEED} // random_bytes(32), }; -} -while (my ($attr, $default) = each %ATTRS_MEMORY_PROTECTION) { - no strict 'refs'; ## no critic (ProhibitNoStrict) - *{$attr} = sub { - my $self = shift; - $self->meta->{$attr} = shift if @_; - $self->meta->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default; - }; -} - -my @ATTRS_OTHER = ( +}; +# has 'headers.transform_seed' => sub { random_bytes(32) }; +# has 'headers.transform_rounds' => 100_000; +# has 'headers.inner_random_stream_key' => sub { random_bytes(32) }; # 64 ? +# has 'headers.inner_random_stream_id' => STREAM_ID_CHACHA20; +# has 'headers.public_custom_data' => {}; + +# META +has 'meta.generator' => '', coerce => \&to_string; +has 'meta.header_hash' => '', coerce => \&to_string; +has 'meta.database_name' => '', coerce => \&to_string; +has 'meta.database_name_changed' => sub { gmtime }, coerce => \&to_time; +has 'meta.database_description' => '', coerce => \&to_string; +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.color' => '', coerce => \&to_string; +has 'meta.master_key_changed' => sub { gmtime }, coerce => \&to_time; +has 'meta.master_key_change_rec' => -1, coerce => \&to_number; +has 'meta.master_key_change_force' => -1, coerce => \&to_number; +# has 'meta.memory_protection' => {}; +has 'meta.custom_icons' => {}; +has 'meta.recycle_bin_enabled' => true, coerce => \&to_bool; +has 'meta.recycle_bin_uuid' => "\0" x 16, coerce => \&to_uuid; +has 'meta.recycle_bin_changed' => sub { gmtime }, coerce => \&to_time; +has 'meta.entry_templates_group' => "\0" x 16, coerce => \&to_uuid; +has 'meta.entry_templates_group_changed' => sub { gmtime }, coerce => \&to_time; +has 'meta.last_selected_group' => "\0" x 16, coerce => \&to_uuid; +has 'meta.last_top_visible_group' => "\0" x 16, coerce => \&to_uuid; +has 'meta.history_max_items' => HISTORY_DEFAULT_MAX_ITEMS, coerce => \&to_number; +has 'meta.history_max_size' => HISTORY_DEFAULT_MAX_SIZE, coerce => \&to_number; +has 'meta.settings_changed' => sub { gmtime }, coerce => \&to_time; +# has 'meta.binaries' => {}; +# has 'meta.custom_data' => {}; + +has 'memory_protection.protect_title' => false, coerce => \&to_bool; +has 'memory_protection.protect_username' => false, coerce => \&to_bool; +has 'memory_protection.protect_password' => true, coerce => \&to_bool; +has 'memory_protection.protect_url' => false, coerce => \&to_bool; +has 'memory_protection.protect_notes' => false, coerce => \&to_bool; +# has 'memory_protection.auto_enable_visual_hiding' => false; + +my @ATTRS = ( HEADER_TRANSFORM_SEED, HEADER_TRANSFORM_ROUNDS, HEADER_INNER_RANDOM_STREAM_KEY, HEADER_INNER_RANDOM_STREAM_ID, + HEADER_PUBLIC_CUSTOM_DATA, ); -sub _set_default_attributes { +sub _set_nonlazy_attributes { my $self = shift; - $self->$_ for keys %ATTRS, keys %ATTRS_HEADERS, keys %ATTRS_META, keys %ATTRS_MEMORY_PROTECTION, - @ATTRS_OTHER; + $self->$_ for list_attributes(ref $self), @ATTRS; } =method memory_protection @@ -907,7 +874,7 @@ ways. Public custom data: =for :list * can store strings, booleans and up to 64-bit integer values (custom data can only store text values) * is NOT encrypted within a KDBX file (hence the "public" part of the name) -* is a flat hash/dict of key-value pairs (no other associated fields like modification times) +* is a plain hash/dict of key-value pairs with no other associated fields (like modification times) =cut