1 package File
::KDBX
::Entry
;
2 # ABSTRACT: A KDBX database entry
7 use Crypt
::Misc
0.029 qw(decode_b64 encode_b32r);
8 use Devel
::GlobalDestruction
;
10 use File
::KDBX
::Constants
qw(:history :icon);
11 use File
::KDBX
::Error
;
12 use File
::KDBX
::Util
qw(:class :coercion :erase :function :uri generate_uuid load_optional);
13 use Hash
::Util
::FieldHash
;
14 use List
::Util
qw(first sum0);
15 use Ref
::Util
qw(is_coderef is_hashref is_plain_hashref);
16 use Scalar
::Util
qw(looks_like_number);
17 use Storable
qw(dclone);
22 extends
'File::KDBX::Object';
24 our $VERSION = '999.999'; # VERSION
26 my $PLACEHOLDER_MAX_DEPTH = 10;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
30 sub _parent_container
{ 'entries' }
34 128-bit UUID identifying the entry within the database
.
38 Integer representing a
default icon
. See L
<File
::KDBX
::Constants
/":icon"> for valid
values.
40 =attr custom_icon_uuid
42 128-bit UUID identifying a custom icon within the database
.
44 =attr foreground_color
46 Text color represented as a string of the form C
<#000000>.
48 =attr background_color
50 Background color represented as a string of the form C
<#FFFFFF>.
58 Text string with arbitrary tags which can be used to build a taxonomy
.
66 data_transfer_obfuscation
=> 0,
67 default_sequence
=> '{USERNAME}{TAB}{PASSWORD}{ENTER}',
70 window
=> 'My Bank - Mozilla Firefox',
71 keystroke_sequence
=> '{PASSWORD}{ENTER}',
76 =attr auto_type_enabled
78 Whether
or not the entry
is eligible to be matched
for auto-typing
.
80 =attr auto_type_data_transfer_obfuscation
84 =attr auto_type_default_sequence
86 The
default auto-type keystroke sequence
.
88 =attr auto_type_associations
90 An array of window title
/ keystroke sequence associations
.
92 =attr previous_parent_group
94 128-bit UUID identifying a group within the database
.
98 Boolean indicating whether the entry password should be tested
for weakness
and show up
in reports
.
102 Hash with entry strings
, including the standard strings as well as any custom ones
.
105 # Every entry has these five strings:
106 Title
=> { value
=> 'Example Entry' },
107 UserName
=> { value
=> 'jdoe' },
108 Password
=> { value
=> 's3cr3t', protect
=> true
},
109 URL
=> { value
=> 'https://example.com' }
110 Notes
=> { value
=> '' },
111 # May also have custom strings:
112 MySystem
=> { value
=> 'The mainframe' },
117 Files
or attachments
.
121 A set of key-value pairs used to store arbitrary data
, usually used by software to keep track of
state rather
122 than by end users
(who typically work with the strings
and binaries
).
126 Array of historical entries
. Historical entries are prior versions of the same entry so they all share the
127 same UUID with the current entry
.
129 =attr last_modification_time
131 Date
and time when the entry was
last modified
.
135 Date
and time when the entry was created
.
137 =attr last_access_time
139 Date
and time when the entry was
last accessed
.
143 Date
and time when the entry expired
or will expire
.
147 Boolean value indicating whether
or not an entry
is expired
.
151 The number of
times an entry
has been used
, which typically means how many
times the B
<Password
> string
has
154 =attr location_changed
156 Date
and time when the entry was
last moved to a different group
.
160 Alias
for the B
<Notes
> string value
.
164 Alias
for the B
<Password
> string value
.
168 Alias
for the B
<Title
> string value
.
172 Alias
for the B
<URL
> string value
.
176 Aliases
for the B
<UserName
> string value
.
182 if (@_ || !defined $self->{uuid
}) {
183 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
184 my $old_uuid = $self->{uuid
};
185 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
186 for my $entry (@{$self->history}) {
187 $entry->{uuid
} = $uuid;
189 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
194 # has uuid => sub { generate_uuid(printable => 1) };
195 has icon_id
=> ICON_PASSWORD
, coerce
=> \
&to_icon_constant
;
196 has custom_icon_uuid
=> undef, coerce
=> \
&to_uuid
;
197 has foreground_color
=> '', coerce
=> \
&to_string
;
198 has background_color
=> '', coerce
=> \
&to_string
;
199 has override_url
=> '', coerce
=> \
&to_string
;
200 has tags
=> '', coerce
=> \
&to_string
;
202 has previous_parent_group
=> undef, coerce
=> \
&to_uuid
;
203 has quality_check
=> true
, coerce
=> \
&to_bool
;
207 # has custom_data => {};
210 has last_modification_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
211 has creation_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
212 has last_access_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
213 has expiry_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
214 has expires
=> false
, store
=> 'times', coerce
=> \
&to_bool
;
215 has usage_count
=> 0, store
=> 'times', coerce
=> \
&to_number
;
216 has location_changed
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
218 # has 'auto_type.auto_type_enabled' => true, coerce => \&to_bool;
219 has 'auto_type_data_transfer_obfuscation' => 0, path
=> 'auto_type.data_transfer_obfuscation',
220 coerce
=> \
&to_number
;
221 has 'auto_type_default_sequence' => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
222 path
=> 'auto_type.default_sequence', coerce
=> \
&to_string
;
223 has 'auto_type_associations' => [], path
=> 'auto_type.associations';
225 my %ATTRS_STRINGS = (
227 username
=> 'UserName',
228 password
=> 'Password',
232 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
233 no strict
'refs'; ## no critic (ProhibitNoStrict)
234 *{$attr} = sub { shift-
>string_value($string_key, @_) };
235 *{"expanded_${attr}"} = sub { shift-
>expanded_string_value($string_key, @_) };
238 my @ATTRS = qw(uuid custom_data history auto_type_enabled);
239 sub _set_nonlazy_attributes
{
241 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes
(ref $self);
248 while (my ($key, $val) = each %args) {
249 if (my $method = $self->can($key)) {
250 $self->$method($val);
253 $self->string($key => $val);
260 ##############################################################################
264 \
%string = $entry->string($string_key);
266 $entry->string($string_key, \
%string);
267 $entry->string($string_key, %attributes);
268 $entry->string($string_key, $value); # same as: value => $value
270 Get
or set a string
. Every string
has a unique
(to the entry
) key
and flags
and so are returned as a hash
271 structure
. For example
:
275 protect
=> true
, # optional
278 Every string should have a value
(but might be C
<undef> due to memory protection
) and these optional flags
282 * C<protect> - Whether or not the string value should be memory-protected.
288 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
289 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
291 if (!defined $args{key
} && !defined $args{value
}) {
292 my %standard = (value
=> 1, protect
=> 1);
293 my @other_keys = grep { !$standard{$_} } keys %args;
294 if (@other_keys == 1) {
295 my $key = $args{key
} = $other_keys[0];
296 $args{value
} = delete $args{$key};
300 my $key = delete $args{key
} or throw
'Must provide a string key to access';
302 return $self->{strings
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
304 while (my ($field, $value) = each %args) {
305 $self->{strings
}{$key}{$field} = $value;
308 # Auto-vivify the standard strings.
309 if ($STANDARD_STRINGS{$key}) {
310 return $self->{strings
}{$key} //= {value
=> '', $self->_protect($key) ? (protect
=> true
) : ()};
312 return $self->{strings
}{$key};
315 ### Get whether or not a standard string is configured to be protected
319 return false
if !$STANDARD_STRINGS{$key};
320 if (my $kdbx = eval { $self->kdbx }) {
321 my $protect = $kdbx->memory_protection($key);
322 return $protect if defined $protect;
324 return $key eq 'Password';
329 $string = $entry->string_value($string_key);
331 Access a string value directly
. The arguments are the same as
for L
</string
>. Returns C
<undef> if the string
332 is not set
or is currently memory-protected
. This
is just a shortcut
for:
335 my $s = $entry->string(...);
336 defined $s ? $s->{value
} : undef;
343 my $string = $self->string(@_) // return undef;
344 return $string->{value
};
347 =method expanded_string_value
349 $string = $entry->expanded_string_value;
351 Same as L
</string_value
> but will substitute placeholders
and resolve field references
. Any placeholders that
352 do not expand to
values are left as-is
.
354 See L
</Placeholders
>.
356 Some placeholders
(notably field references
) require the entry be connected to a database
and will throw an
361 sub _expand_placeholder
{
363 my $placeholder = shift;
368 my $placeholder_key = $placeholder;
370 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
373 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
375 my $local_key = join('/', Hash
::Util
::FieldHash
::id
($self), $placeholder_key);
376 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
377 my $handler = $File::KDBX
::PLACEHOLDERS
{$placeholder_key} or next;
378 memoize recurse_limit
($handler, $PLACEHOLDER_MAX_DEPTH, sub {
379 alert
"Detected deep recursion while expanding $placeholder placeholder",
380 placeholder
=> $placeholder;
385 return $handler->($self, $arg, $placeholder);
392 my $expand = memoize
$self->can('_expand_placeholder'), $self;
394 # placeholders (including field references):
395 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
397 # environment variables (alt syntax):
398 my $vars = join('|', map { quotemeta($_) } keys %ENV);
399 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
404 sub expanded_string_value
{
406 my $str = $self->string_peek(@_) // return undef;
407 my $cleanup = erase_scoped
$str;
408 return $self->_expand_string($str);
411 =method other_strings
413 $other = $entry->other_strings;
414 $other = $entry->other_strings($delimiter);
416 Get a concatenation of all non-standard string
values. The
default delimiter
is a newline
. This
is is useful
417 for executing queries to search
for entities based on the contents of these other strings
(if any
).
423 my $delim = shift // "\n";
425 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
426 return join($delim, @strings);
431 $string = $entry->string_peek($string_key);
433 Same as L
</string_value
> but can also retrieve the value from protected-memory
if the value
is currently
440 my $string = $self->string(@_);
441 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
444 ##############################################################################
446 sub add_auto_type_association
{
448 my $association = shift;
449 push @{$self->auto_type_associations}, $association;
452 sub expand_keystroke_sequence
{
454 my $association = shift;
456 my $keys = is_hashref
($association) && exists $association->{keystroke_sequence
} ?
457 $association->{keystroke_sequence
} : defined $association ? $association : '';
459 $keys = $self->auto_type_default_sequence if !$keys;
460 # TODO - Fall back to getting default sequence from parent group, which probably means we shouldn't be
461 # setting a default value in the entry..
463 return $self->_expand_string($keys);
466 ##############################################################################
470 my $key = shift or throw
'Must provide a binary key to access';
472 my $arg = @_ == 1 ? shift : undef;
474 @args{keys %$arg} = values %$arg if ref $arg eq 'HASH';
475 $args{value
} = $arg if !ref $arg;
476 while (my ($field, $value) = each %args) {
477 $self->{binaries
}{$key}{$field} = $value;
480 my $binary = $self->{binaries
}{$key} //= {value
=> ''};
481 if (defined (my $ref = $binary->{ref})) {
482 $binary = $self->{binaries
}{$key} = dclone
($self->kdbx->binaries->{$ref});
487 sub binary_novivify
{
489 my $binary_key = shift;
490 return if !$self->{binaries
}{$binary_key} && !@_;
491 return $self->binary($binary_key, @_);
496 my $binary = $self->binary_novivify(@_) // return undef;
497 return $binary->{value
};
500 sub searching_enabled
{
502 my $parent = $self->group;
503 return $parent->effective_enable_searching if $parent;
507 sub auto_type_enabled
{
509 $self->auto_type->{enabled
} = to_bool
(shift) if @_;
510 $self->auto_type->{enabled
} //= true
;
511 return false
if !$self->auto_type->{enabled
};
512 return true
if !$self->is_connected;
513 my $parent = $self->group;
514 return $parent->effective_enable_auto_type if $parent;
518 ##############################################################################
522 $otp = $entry->hmac_otp(%options);
524 Generate an HMAC-based one-time password
, or C
<undef> if HOTP
is not configured
for the entry
. The entry
's
525 strings generally must first be unprotected, just like when accessing the password. Valid options are:
528 * C<counter> - Specify the counter value
530 To configure HOTP, see L</"One-time Passwords">.
536 load_optional('Pass
::OTP
');
538 my %params = ($self->_hotp_params, @_);
539 return if !defined $params{type} || !defined $params{secret};
541 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
544 my $otp = eval {Pass::OTP::otp(%params, @_) };
546 throw 'Unable to generate HOTP
', error => $err;
549 $self->_hotp_increment_counter($params{counter});
556 $otp = $entry->time_otp(%options);
558 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
559 strings generally must first be unprotected
, just like
when accessing the password
. Valid options are
:
562 * C<now> - Specify the value for determining the time-step counter
564 To configure TOTP, see L</"One-time Passwords">.
570 load_optional
('Pass::OTP');
572 my %params = ($self->_totp_params, @_);
573 return if !defined $params{type
} || !defined $params{secret
};
575 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
578 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
580 throw
'Unable to generate TOTP', error
=> $err;
590 $uri_string = $entry->hmac_otp_uri;
591 $uri_string = $entry->time_otp_uri;
593 Get a HOTP
or TOTP otpauth URI
for the entry
, if available
.
595 To configure OTP
, see L
</"One-time Passwords">.
599 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
600 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
606 return if 4 != grep { defined } @params{qw(type secret issuer account)};
607 return if $params{type
} !~ /^[ht]otp$/i;
609 my $label = delete $params{label
};
610 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
612 my $type = lc($params{type
});
613 my $issuer = $params{issuer
};
614 my $account = $params{account
};
616 $label //= "$issuer:$account";
618 my $secret = $params{secret
};
619 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
621 delete $params{algorithm
} if defined $params{algorithm
} && $params{algorithm
} eq 'sha1';
622 delete $params{period
} if defined $params{period
} && $params{period
} == 30;
623 delete $params{digits
} if defined $params{digits
} && $params{digits
} == 6;
624 delete $params{counter
} if defined $params{counter
} && $params{counter
} == 0;
626 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
628 if (defined $params{encoder
}) {
629 $uri .= "&encoder=$params{encoder}";
632 $uri .= '&algorithm=' . uc($params{algorithm
}) if defined $params{algorithm
};
633 $uri .= "&digits=$params{digits}" if defined $params{digits
};
634 $uri .= "&counter=$params{counter}" if defined $params{counter
};
635 $uri .= "&period=$params{period}" if defined $params{period
};
645 issuer
=> $self->title || 'KDBX',
646 account
=> $self->username || 'none',
648 counter
=> $self->string_value('HmacOtp-Counter') // 0,
649 $self->_otp_secret_params('Hmac'),
651 return %params if $params{secret
};
653 my %otp_params = $self->_otp_params;
654 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
656 # $otp_params{counter} = 0
658 return (%params, %otp_params);
665 'HMAC-SHA-1' => 'sha1',
666 'HMAC-SHA-256' => 'sha256',
667 'HMAC-SHA-512' => 'sha512',
671 issuer
=> $self->title || 'KDBX',
672 account
=> $self->username || 'none',
673 digits
=> $self->string_value('TimeOtp-Length') // 6,
674 algorithm
=> $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
675 period
=> $self->string_value('TimeOtp-Period') // 30,
676 $self->_otp_secret_params('Time'),
678 return %params if $params{secret
};
680 my %otp_params = $self->_otp_params;
681 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
683 return (%params, %otp_params);
689 load_optional
('Pass::OTP::URI');
691 my $uri = $self->string_value('otp') || '';
693 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
694 return () if !$params{secret
} || !$params{type
};
696 if (($params{encoder
} // '') eq 'steam') {
698 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
701 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
702 my ($issuer, $user) = split(':', $params{label
} // ':', 2);
703 $params{issuer
} //= uri_unescape_utf8
($issuer);
704 $params{account
} //= uri_unescape_utf8
($user);
706 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
707 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
712 sub _otp_secret_params
{
714 my $type = shift // return ();
716 my $secret_txt = $self->string_value("${type}Otp-Secret");
717 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
718 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
719 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
721 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
722 return () if $count == 0;
723 alert
"Found multiple ${type}Otp-Secret strings", count
=> $count if 1 < $count;
725 return (secret
=> $secret_b32, base32
=> 1) if defined $secret_b32;
726 return (secret
=> decode_b64
($secret_b64)) if defined $secret_b64;
727 return (secret
=> pack('H*', $secret_hex)) if defined $secret_hex;
728 return (secret
=> encode
('UTF-8', $secret_txt));
731 sub _hotp_increment_counter
{
733 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
735 looks_like_number
($counter) or throw
'HmacOtp-Counter value must be a number', value
=> $counter;
736 my $next = $counter + 1;
737 $self->string('HmacOtp-Counter', $next);
741 ##############################################################################
745 $size = $entry->size;
747 Get the size
(in bytes
) of an entry
.
749 B
<NOTE
:> This
is not an exact figure because there
is no canonical serialization of an entry
. This size should
750 only be used as a rough estimate
for comparison with other entries
or to impose data size limitations
.
760 $size += length(encode
('UTF-8', $self->tags // ''));
762 # attributes (strings)
763 while (my ($key, $string) = each %{$self->strings}) {
764 next if !defined $string->{value
};
765 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $string->{value
} // ''));
769 while (my ($key, $item) = each %{$self->custom_data}) {
770 next if !defined $item->{value
};
771 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $item->{value
} // ''));
775 while (my ($key, $binary) = each %{$self->binaries}) {
776 next if !defined $binary->{value
};
777 my $value_len = utf8
::is_utf8
($binary->{value
}) ? length(encode
('UTF-8', $binary->{value
}))
778 : length($binary->{value
});
779 $size += length(encode
('UTF-8', $key)) + $value_len;
782 # autotype associations
783 for my $association (@{$self->auto_type->{associations
} || []}) {
784 $size += length(encode
('UTF-8', $association->{window
}))
785 + length(encode
('UTF-8', $association->{keystroke_sequence
} // ''));
791 ##############################################################################
795 my $entries = $self->{history
} //= [];
796 # FIXME - Looping through entries on each access is too expensive.
797 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
803 $size = $entry->history_size;
805 Get the size
(in bytes
) of all historical entries combined
.
811 return sum0
map { $_->size } @{$self->history};
814 =method prune_history
816 $entry->prune_history(%options);
818 Remove as many older historical entries as necessary to get under the database limits
. The limits are taken
819 from the connected database
(if any
) or can be overridden with C
<%options>:
822 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
823 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
831 my $max_items = $args{max_items
} // eval { $self->kdbx->history_max_items }
832 // HISTORY_DEFAULT_MAX_ITEMS
;
833 my $max_size = $args{max_size
} // eval { $self->kdbx->history_max_size }
834 // HISTORY_DEFAULT_MAX_SIZE
;
836 # history is ordered oldest to youngest
837 my $history = $self->history;
839 if (0 <= $max_items && $max_items < @$history) {
840 splice @$history, -$max_items;
843 if (0 <= $max_size) {
844 my $current_size = $self->history_size;
845 while ($max_size < $current_size) {
846 my $entry = shift @$history;
847 $current_size -= $entry->size;
852 =method add_historical_entry
854 $entry->add_historical_entry($entry);
856 Add an entry to the history
.
860 sub add_historical_entry
{
862 delete $_->{history
} for @_;
863 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
866 =method current_entry
868 $current_entry = $entry->current_entry;
870 Get an entry
's current entry. If the entry itself is current (not historical), itself is returned.
876 my $group = $self->group;
879 my $id = $self->uuid;
880 my $entry = first { $id eq $_->uuid } @{$group->entries};
881 return $entry if $entry;
889 $bool = $entry->is_current;
891 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
892 in the parent group's entry list
.
898 my $current = $self->current_entry;
899 return Hash
::Util
::FieldHash
::id
($self) == Hash
::Util
::FieldHash
::id
($current);
902 =method is_historical
904 $bool = $entry->is_historical;
906 Get whether
or not an entry
is considered historical
(i
.e
. not current
).
908 This
is just the inverse of L
</is_current
>.
912 sub is_historical
{ !$_[0]->is_current }
914 ##############################################################################
919 return $self->SUPER::_signal
("entry.$type", @_);
925 $self->add_historical_entry($orig);
927 $self->last_modification_time($time);
928 $self->last_access_time($time);
931 sub label
{ shift-
>expanded_title(@_) }
938 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
939 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
940 that every entry has:
949 Beyond this, you can store any number of other strings and any number of binaries that you can use for
950 whatever purpose you want.
952 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
953 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
954 the attributes to see what's available.
956 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
960 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
961 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
962 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
963 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
964 C<http://example.com?user=batman>.
966 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
967 brace, like C<{PLACEHOLDER:ARGUMENT}>.
969 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
970 This software supports many (but not all) of the placeholders documented there.
972 =head3 Entry Placeholders
975 * ☑ C<{TITLE}> - B<Title> string
976 * ☑ C<{USERNAME}> - B<UserName> string
977 * ☑ C<{PASSWORD}> - B<Password> string
978 * ☑ C<{NOTES}> - B<Notes> string
979 * ☑ C<{URL}> - B<URL> string
980 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
981 * ☑ C<{URL:USERINFO}>
982 * ☑ C<{URL:USERNAME}>
983 * ☑ C<{URL:PASSWORD}>
988 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
989 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
990 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
991 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
992 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
993 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
994 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
995 * ☑ C<{GROUP_PATH}> - Full path of the parent group
996 * ☑ C<{GROUP}> - Name of the parent group
998 =head3 Field References
1001 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
1003 =head3 File path Placeholders
1006 * ☑ C<{APPDIR}> - Program directory path
1007 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
1008 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
1009 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
1010 * ☑ C<{OPERA}> - Path to the Opera browser executable
1011 * ☑ C<{SAFARI}> - Path to the Safari browser executable
1012 * ☒ C<{DB_PATH}> - Full file path of the database
1013 * ☒ C<{DB_DIR}> - Directory path of the database
1014 * ☒ C<{DB_NAME}> - File name (including extension) of the database
1015 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
1016 * ☒ C<{DB_EXT}> - File name extension
1017 * ☑ C<{ENV_DIRSEP}> - Directory separator
1018 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
1020 =head3 Date and Time Placeholders
1023 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
1024 * ☑ C<{DT_YEAR}> - Year component of the current local date
1025 * ☑ C<{DT_MONTH}> - Month component of the current local date
1026 * ☑ C<{DT_DAY}> - Day component of the current local date
1027 * ☑ C<{DT_HOUR}> - Hour component of the current local time
1028 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
1029 * ☑ C<{DT_SECOND}> - Second component of the current local time
1030 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
1031 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
1032 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
1033 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
1034 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
1035 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
1036 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
1038 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
1040 =head3 Special Key Placeholders
1042 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
1043 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
1044 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
1046 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
1047 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
1049 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
1050 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
1052 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
1053 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
1055 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
1056 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
1058 =head3 Miscellaneous Placeholders
1062 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
1063 * ☒ C<{BASE:USERINFO}>
1064 * ☒ C<{BASE:USERNAME}>
1065 * ☒ C<{BASE:PASSWORD}>
1070 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1071 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1072 * ☒ C<{CLIPBOARD-SET:/Text/}>
1074 * ☒ C<{CMD:/CommandLine/Options/}>
1075 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1076 * ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1077 * ☒ C<{GROUP_SEL_NOTES}>
1078 * ☒ C<{GROUP_SEL_PATH}>
1080 * ☒ C<{NEWPASSWORD}>
1081 * ☒ C<{NEWPASSWORD:/Profile/}>
1082 * ☒ C<{PASSWORD_ENC}>
1084 * ☒ C<{PICKCHARS:Field:Options}>
1086 * ☒ C<{T-CONV:/Text/Type/}>
1087 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1089 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1090 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1091 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1092 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1094 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1099 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1100 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1101 strings or auto-complete key sequences.
1103 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1104 my ($entry, $arg) = @_; # ^ Notice the colon here
1108 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1109 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1110 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1112 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1113 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1114 both with and without a colon (or they could be different subroutines):
1116 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1117 (undef, my $arg) = @_;
1118 return defined $arg ? rand($arg) : rand;
1121 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1124 %File::KDBX::PLACEHOLDERS = ();
1126 =head2 One-time Passwords
1128 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1129 configuration storage isn't completely standardized, but this module supports two predominant configuration
1133 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1136 B<NOTE:> To use this feature, you must install the suggested dependency:
1141 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1142 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1144 To configure TOTP in the KeePass 2 style, set the following strings:
1147 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1149 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1150 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1151 * C<TimeOtp-Secret> - Text string secret, OR
1152 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1153 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1154 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1156 To configure HOTP in the KeePass 2 style, set the following strings:
1159 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1161 * C<HmacOtp-Secret> - Text string secret, OR
1162 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1163 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1164 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1166 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1167 these should actually be set or an error will be thrown.
1169 Here's a basic example:
1171 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1173 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1175 my $otp = $entry->time_otp;