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 :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_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 previous_parent_group
78 128-bit UUID identifying a group within the database
.
82 Boolean indicating whether the entry password should be tested
for weakness
and show up
in reports
.
86 Hash with entry strings
, including the standard strings as well as any custom ones
.
89 # Every entry has these five strings:
90 Title
=> { value
=> 'Example Entry' },
91 UserName
=> { value
=> 'jdoe' },
92 Password
=> { value
=> 's3cr3t', protect
=> true
},
93 URL
=> { value
=> 'https://example.com' }
94 Notes
=> { value
=> '' },
95 # May also have custom strings:
96 MySystem
=> { value
=> 'The mainframe' },
101 Files
or attachments
.
105 A set of key-value pairs used to store arbitrary data
, usually used by software to keep track of
state rather
106 than by end users
(who typically work with the strings
and binaries
).
110 Array of historical entries
. Historical entries are prior versions of the same entry so they all share the
111 same UUID with the current entry
.
113 =attr last_modification_time
115 Date
and time when the entry was
last modified
.
119 Date
and time when the entry was created
.
121 =attr last_access_time
123 Date
and time when the entry was
last accessed
.
127 Date
and time when the entry expired
or will expire
.
131 Boolean value indicating whether
or not an entry
is expired
.
135 The number of
times an entry
has been used
, which typically means how many
times the B
<Password
> string
has
138 =attr location_changed
140 Date
and time when the entry was
last moved to a different group
.
144 Alias
for the B
<Notes
> string value
.
148 Alias
for the B
<Password
> string value
.
152 Alias
for the B
<Title
> string value
.
156 Alias
for the B
<URL
> string value
.
160 Aliases
for the B
<UserName
> string value
.
166 if (@_ || !defined $self->{uuid
}) {
167 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
168 my $old_uuid = $self->{uuid
};
169 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
170 for my $entry (@{$self->history}) {
171 $entry->{uuid
} = $uuid;
173 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
178 # has uuid => sub { generate_uuid(printable => 1) };
179 has icon_id
=> ICON_PASSWORD
, coerce
=> \
&to_icon_constant
;
180 has custom_icon_uuid
=> undef, coerce
=> \
&to_uuid
;
181 has foreground_color
=> '', coerce
=> \
&to_string
;
182 has background_color
=> '', coerce
=> \
&to_string
;
183 has override_url
=> '', coerce
=> \
&to_string
;
184 has tags
=> '', coerce
=> \
&to_string
;
186 has previous_parent_group
=> undef, coerce
=> \
&to_uuid
;
187 has quality_check
=> true
, coerce
=> \
&to_bool
;
191 # has custom_data => {};
194 has last_modification_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
195 has creation_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
196 has last_access_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
197 has expiry_time
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
198 has expires
=> false
, store
=> 'times', coerce
=> \
&to_bool
;
199 has usage_count
=> 0, store
=> 'times', coerce
=> \
&to_number
;
200 has location_changed
=> sub { gmtime }, store
=> 'times', coerce
=> \
&to_time
;
202 my %ATTRS_STRINGS = (
204 username
=> 'UserName',
205 password
=> 'Password',
209 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
210 no strict
'refs'; ## no critic (ProhibitNoStrict)
211 *{$attr} = sub { shift-
>string_value($string_key, @_) };
212 *{"expanded_${attr}"} = sub { shift-
>expanded_string_value($string_key, @_) };
215 my @ATTRS = qw(uuid custom_data history);
216 sub _set_nonlazy_attributes
{
218 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes
(ref $self);
225 while (my ($key, $val) = each %args) {
226 if (my $method = $self->can($key)) {
227 $self->$method($val);
230 $self->string($key => $val);
237 ##############################################################################
241 \
%string = $entry->string($string_key);
243 $entry->string($string_key, \
%string);
244 $entry->string($string_key, %attributes);
245 $entry->string($string_key, $value); # same as: value => $value
247 Get
or set a string
. Every string
has a unique
(to the entry
) key
and flags
and so are returned as a hash
248 structure
. For example
:
252 protect
=> true
, # optional
255 Every string should have a value
(but might be C
<undef> due to memory protection
) and these optional flags
259 * C<protect> - Whether or not the string value should be memory-protected.
265 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
266 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
268 if (!defined $args{key
} && !defined $args{value
}) {
269 my %standard = (value
=> 1, protect
=> 1);
270 my @other_keys = grep { !$standard{$_} } keys %args;
271 if (@other_keys == 1) {
272 my $key = $args{key
} = $other_keys[0];
273 $args{value
} = delete $args{$key};
277 my $key = delete $args{key
} or throw
'Must provide a string key to access';
279 return $self->{strings
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
281 while (my ($field, $value) = each %args) {
282 $self->{strings
}{$key}{$field} = $value;
285 # Auto-vivify the standard strings.
286 if ($STANDARD_STRINGS{$key}) {
287 return $self->{strings
}{$key} //= {value
=> '', $self->_protect($key) ? (protect
=> true
) : ()};
289 return $self->{strings
}{$key};
292 ### Get whether or not a standard string is configured to be protected
296 return false
if !$STANDARD_STRINGS{$key};
297 if (my $kdbx = eval { $self->kdbx }) {
298 my $protect = $kdbx->memory_protection($key);
299 return $protect if defined $protect;
301 return $key eq 'Password';
306 $string = $entry->string_value;
308 Access a string value directly
. Returns C
<undef> if the string
is not set
.
314 my $string = $self->string(@_) // return undef;
315 return $string->{value
};
318 =method expanded_string_value
320 $string = $entry->expanded_string_value;
322 Same as L
</string_value
> but will substitute placeholders
and resolve field references
. Any placeholders that
323 do not expand to
values are left as-is
.
325 See L
</Placeholders
>.
327 Some placeholders
(notably field references
) require the entry be associated with a database
and will throw an
328 error
if there
is no association
.
332 sub _expand_placeholder
{
334 my $placeholder = shift;
339 my $placeholder_key = $placeholder;
341 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
344 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
346 my $local_key = join('/', Hash
::Util
::FieldHash
::id
($self), $placeholder_key);
347 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
348 my $handler = $File::KDBX
::PLACEHOLDERS
{$placeholder_key} or next;
349 memoize recurse_limit
($handler, $PLACEHOLDER_MAX_DEPTH, sub {
350 alert
"Detected deep recursion while expanding $placeholder placeholder",
351 placeholder
=> $placeholder;
356 return $handler->($self, $arg, $placeholder);
363 my $expand = memoize
$self->can('_expand_placeholder'), $self;
365 # placeholders (including field references):
366 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
368 # environment variables (alt syntax):
369 my $vars = join('|', map { quotemeta($_) } keys %ENV);
370 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
375 sub expanded_string_value
{
377 my $str = $self->string_value(@_) // return undef;
378 return $self->_expand_string($str);
381 =method other_strings
383 $other = $entry->other_strings;
384 $other = $entry->other_strings($delimiter);
386 Get a concatenation of all non-standard string
values. The
default delimiter
is a newline
. This
is is useful
387 for executing queries to search
for entities based on the contents of these other strings
(if any
).
393 my $delim = shift // "\n";
395 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
396 return join($delim, @strings);
401 my $string = $self->string(@_);
402 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
405 sub password_peek
{ $_[0]->string_peek('Password') }
407 ##############################################################################
411 my $key = shift or throw
'Must provide a binary key to access';
413 my $arg = @_ == 1 ? shift : undef;
415 @args{keys %$arg} = values %$arg if ref $arg eq 'HASH';
416 $args{value
} = $arg if !ref $arg;
417 while (my ($field, $value) = each %args) {
418 $self->{binaries
}{$key}{$field} = $value;
421 my $binary = $self->{binaries
}{$key} //= {value
=> ''};
422 if (defined (my $ref = $binary->{ref})) {
423 $binary = $self->{binaries
}{$key} = dclone
($self->kdbx->binaries->{$ref});
428 sub binary_novivify
{
430 my $binary_key = shift;
431 return if !$self->{binaries
}{$binary_key} && !@_;
432 return $self->binary($binary_key, @_);
437 my $binary = $self->binary_novivify(@_) // return undef;
438 return $binary->{value
};
441 sub searching_enabled
{
443 my $parent = $self->parent;
444 return $parent->effective_enable_searching if $parent;
448 sub auto_type_enabled
{
450 return false
if !$self->auto_type->{enabled
};
451 my $parent = $self->parent;
452 return $parent->effective_enable_auto_type if $parent;
456 ##############################################################################
460 $otp = $entry->hmac_otp(%options);
462 Generate an HMAC-based one-time password
, or C
<undef> if HOTP
is not configured
for the entry
. The entry
's
463 strings generally must first be unprotected, just like when accessing the password. Valid options are:
466 * C<counter> - Specify the counter value
468 To configure HOTP, see L</"One-time Passwords">.
474 load_optional('Pass
::OTP
');
476 my %params = ($self->_hotp_params, @_);
477 return if !defined $params{type} || !defined $params{secret};
479 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
482 my $otp = eval {Pass::OTP::otp(%params, @_) };
484 throw 'Unable to generate HOTP
', error => $err;
487 $self->_hotp_increment_counter($params{counter});
494 $otp = $entry->time_otp(%options);
496 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
497 strings generally must first be unprotected
, just like
when accessing the password
. Valid options are
:
500 * C<now> - Specify the value for determining the time-step counter
502 To configure TOTP, see L</"One-time Passwords">.
508 load_optional
('Pass::OTP');
510 my %params = ($self->_totp_params, @_);
511 return if !defined $params{type
} || !defined $params{secret
};
513 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
516 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
518 throw
'Unable to generate TOTP', error
=> $err;
528 $uri_string = $entry->hmac_otp_uri;
529 $uri_string = $entry->time_otp_uri;
531 Get a HOTP
or TOTP otpauth URI
for the entry
, if available
.
533 To configure OTP
, see L
</"One-time Passwords">.
537 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
538 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
544 return if 4 != grep { defined } @params{qw(type secret issuer account)};
545 return if $params{type
} !~ /^[ht]otp$/i;
547 my $label = delete $params{label
};
548 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
550 my $type = lc($params{type
});
551 my $issuer = $params{issuer
};
552 my $account = $params{account
};
554 $label //= "$issuer:$account";
556 my $secret = $params{secret
};
557 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
559 delete $params{algorithm
} if defined $params{algorithm
} && $params{algorithm
} eq 'sha1';
560 delete $params{period
} if defined $params{period
} && $params{period
} == 30;
561 delete $params{digits
} if defined $params{digits
} && $params{digits
} == 6;
562 delete $params{counter
} if defined $params{counter
} && $params{counter
} == 0;
564 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
566 if (defined $params{encoder
}) {
567 $uri .= "&encoder=$params{encoder}";
570 $uri .= '&algorithm=' . uc($params{algorithm
}) if defined $params{algorithm
};
571 $uri .= "&digits=$params{digits}" if defined $params{digits
};
572 $uri .= "&counter=$params{counter}" if defined $params{counter
};
573 $uri .= "&period=$params{period}" if defined $params{period
};
583 issuer
=> $self->title || 'KDBX',
584 account
=> $self->username || 'none',
586 counter
=> $self->string_value('HmacOtp-Counter') // 0,
587 $self->_otp_secret_params('Hmac'),
589 return %params if $params{secret
};
591 my %otp_params = $self->_otp_params;
592 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
594 # $otp_params{counter} = 0
596 return (%params, %otp_params);
603 'HMAC-SHA-1' => 'sha1',
604 'HMAC-SHA-256' => 'sha256',
605 'HMAC-SHA-512' => 'sha512',
609 issuer
=> $self->title || 'KDBX',
610 account
=> $self->username || 'none',
611 digits
=> $self->string_value('TimeOtp-Length') // 6,
612 algorithm
=> $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
613 period
=> $self->string_value('TimeOtp-Period') // 30,
614 $self->_otp_secret_params('Time'),
616 return %params if $params{secret
};
618 my %otp_params = $self->_otp_params;
619 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
621 return (%params, %otp_params);
627 load_optional
('Pass::OTP::URI');
629 my $uri = $self->string_value('otp') || '';
631 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
632 return () if !$params{secret
} || !$params{type
};
634 if (($params{encoder
} // '') eq 'steam') {
636 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
639 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
640 my ($issuer, $user) = split(':', $params{label
} // ':', 2);
641 $params{issuer
} //= uri_unescape_utf8
($issuer);
642 $params{account
} //= uri_unescape_utf8
($user);
644 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
645 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
650 sub _otp_secret_params
{
652 my $type = shift // return ();
654 my $secret_txt = $self->string_value("${type}Otp-Secret");
655 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
656 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
657 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
659 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
660 return () if $count == 0;
661 alert
"Found multiple ${type}Otp-Secret strings", count
=> $count if 1 < $count;
663 return (secret
=> $secret_b32, base32
=> 1) if defined $secret_b32;
664 return (secret
=> decode_b64
($secret_b64)) if defined $secret_b64;
665 return (secret
=> pack('H*', $secret_hex)) if defined $secret_hex;
666 return (secret
=> encode
('UTF-8', $secret_txt));
669 sub _hotp_increment_counter
{
671 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
673 looks_like_number
($counter) or throw
'HmacOtp-Counter value must be a number', value
=> $counter;
674 my $next = $counter + 1;
675 $self->string('HmacOtp-Counter', $next);
679 ##############################################################################
683 $size = $entry->size;
685 Get the size
(in bytes
) of an entry
.
687 B
<NOTE
:> This
is not an exact figure because there
is no canonical serialization of an entry
. This size should
688 only be used as a rough estimate
for comparison with other entries
or to impose data size limitations
.
698 $size += length(encode
('UTF-8', $self->tags // ''));
700 # attributes (strings)
701 while (my ($key, $string) = each %{$self->strings}) {
702 next if !defined $string->{value
};
703 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $string->{value
} // ''));
707 while (my ($key, $item) = each %{$self->custom_data}) {
708 next if !defined $item->{value
};
709 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $item->{value
} // ''));
713 while (my ($key, $binary) = each %{$self->binaries}) {
714 next if !defined $binary->{value
};
715 my $value_len = utf8
::is_utf8
($binary->{value
}) ? length(encode
('UTF-8', $binary->{value
}))
716 : length($binary->{value
});
717 $size += length(encode
('UTF-8', $key)) + $value_len;
720 # autotype associations
721 for my $association (@{$self->auto_type->{associations
} || []}) {
722 $size += length(encode
('UTF-8', $association->{window
}))
723 + length(encode
('UTF-8', $association->{keystroke_sequence
} // ''));
729 ##############################################################################
733 my $entries = $self->{history
} //= [];
734 # FIXME - Looping through entries on each access is too expensive.
735 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
741 $size = $entry->history_size;
743 Get the size
(in bytes
) of all historical entries combined
.
749 return sum0
map { $_->size } @{$self->history};
752 =method prune_history
754 $entry->prune_history(%options);
756 Remove as many older historical entries as necessary to get under the database limits
. The limits are taken
757 from the associated database
(if any
) or can be overridden with C
<%options>:
760 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
761 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
769 my $max_items = $args{max_items
} // eval { $self->kdbx->history_max_items }
770 // HISTORY_DEFAULT_MAX_ITEMS
;
771 my $max_size = $args{max_size
} // eval { $self->kdbx->history_max_size }
772 // HISTORY_DEFAULT_MAX_SIZE
;
774 # history is ordered oldest to youngest
775 my $history = $self->history;
777 if (0 <= $max_items && $max_items < @$history) {
778 splice @$history, -$max_items;
781 if (0 <= $max_size) {
782 my $current_size = $self->history_size;
783 while ($max_size < $current_size) {
784 my $entry = shift @$history;
785 $current_size -= $entry->size;
790 =method add_historical_entry
792 $entry->add_historical_entry($entry);
794 Add an entry to the history
.
798 sub add_historical_entry
{
800 delete $_->{history
} for @_;
801 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
804 =method current_entry
806 $current_entry = $entry->current_entry;
808 Get an entry
's current entry. If the entry itself is current (not historical), itself is returned.
814 my $group = $self->parent;
817 my $id = $self->uuid;
818 my $entry = first { $id eq $_->uuid } @{$group->entries};
819 return $entry if $entry;
827 $bool = $entry->is_current;
829 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
830 in the parent group's entry list
.
836 my $current = $self->current_entry;
837 return Hash
::Util
::FieldHash
::id
($self) == Hash
::Util
::FieldHash
::id
($current);
840 =method is_historical
842 $bool = $entry->is_historical;
844 Get whether
or not an entry
is considered historical
(i
.e
. not current
).
846 This
is just the inverse of L
</is_current
>.
850 sub is_historical
{ !$_[0]->is_current }
852 ##############################################################################
857 return $self->SUPER::_signal
("entry.$type", @_);
863 $self->add_historical_entry($orig);
865 $self->last_modification_time($time);
866 $self->last_access_time($time);
869 sub label
{ shift-
>expanded_title(@_) }
876 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
877 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
878 that every entry has:
887 Beyond this, you can store any number of other strings and any number of binaries that you can use for
888 whatever purpose you want.
890 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
891 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
892 the attributes to see what's available.
894 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
898 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
899 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
900 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
901 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
902 C<http://example.com?user=batman>.
904 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
905 brace, like C<{PLACEHOLDER:ARGUMENT}>.
907 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
908 This software supports many (but not all) of the placeholders documented there.
910 =head3 Entry Placeholders
913 * ☑ C<{TITLE}> - B<Title> string
914 * ☑ C<{USERNAME}> - B<UserName> string
915 * ☑ C<{PASSWORD}> - B<Password> string
916 * ☑ C<{NOTES}> - B<Notes> string
917 * ☑ C<{URL}> - B<URL> string
918 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
919 * ☑ C<{URL:USERINFO}>
920 * ☑ C<{URL:USERNAME}>
921 * ☑ C<{URL:PASSWORD}>
926 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
927 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
928 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
929 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
930 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
931 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
932 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
933 * ☑ C<{GROUP_PATH}> - Full path of the parent group
934 * ☑ C<{GROUP}> - Name of the parent group
936 =head3 Field References
939 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
941 =head3 File path Placeholders
944 * ☑ C<{APPDIR}> - Program directory path
945 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
946 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
947 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
948 * ☑ C<{OPERA}> - Path to the Opera browser executable
949 * ☑ C<{SAFARI}> - Path to the Safari browser executable
950 * ☒ C<{DB_PATH}> - Full file path of the database
951 * ☒ C<{DB_DIR}> - Directory path of the database
952 * ☒ C<{DB_NAME}> - File name (including extension) of the database
953 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
954 * ☒ C<{DB_EXT}> - File name extension
955 * ☑ C<{ENV_DIRSEP}> - Directory separator
956 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
958 =head3 Date and Time Placeholders
961 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
962 * ☑ C<{DT_YEAR}> - Year component of the current local date
963 * ☑ C<{DT_MONTH}> - Month component of the current local date
964 * ☑ C<{DT_DAY}> - Day component of the current local date
965 * ☑ C<{DT_HOUR}> - Hour component of the current local time
966 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
967 * ☑ C<{DT_SECOND}> - Second component of the current local time
968 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
969 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
970 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
971 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
972 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
973 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
974 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
976 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
978 =head3 Special Key Placeholders
980 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
981 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
982 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
984 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
985 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
987 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
988 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
990 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
991 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
993 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
994 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
996 =head3 Miscellaneous Placeholders
1000 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
1001 * ☒ C<{BASE:USERINFO}>
1002 * ☒ C<{BASE:USERNAME}>
1003 * ☒ C<{BASE:PASSWORD}>
1008 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1009 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1010 * ☒ C<{CLIPBOARD-SET:/Text/}>
1012 * ☒ C<{CMD:/CommandLine/Options/}>
1013 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1014 * ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1015 * ☒ C<{GROUP_SEL_NOTES}>
1016 * ☒ C<{GROUP_SEL_PATH}>
1018 * ☒ C<{NEWPASSWORD}>
1019 * ☒ C<{NEWPASSWORD:/Profile/}>
1020 * ☒ C<{PASSWORD_ENC}>
1022 * ☒ C<{PICKCHARS:Field:Options}>
1024 * ☒ C<{T-CONV:/Text/Type/}>
1025 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1027 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1028 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1029 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1030 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1032 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1037 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1038 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1039 strings or auto-complete key sequences.
1041 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1042 my ($entry, $arg) = @_; # ^ Notice the colon here
1046 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1047 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1048 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1050 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1051 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1052 both with and without a colon (or they could be different subroutines):
1054 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1055 (undef, my $arg) = @_;
1056 return defined $arg ? rand($arg) : rand;
1059 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1062 %File::KDBX::PLACEHOLDERS = ();
1064 =head2 One-time Passwords
1066 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1067 configuration storage isn't completely standardized, but this module supports two predominant configuration
1071 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1074 B<NOTE:> To use this feature, you must install the suggested dependency:
1079 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1080 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1082 To configure TOTP in the KeePass 2 style, set the following strings:
1085 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1087 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1088 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1089 * C<TimeOtp-Secret> - Text string secret, OR
1090 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1091 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1092 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1094 To configure HOTP in the KeePass 2 style, set the following strings:
1097 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1099 * C<HmacOtp-Secret> - Text string secret, OR
1100 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1101 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1102 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1104 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1105 these should actually be set or an error will be thrown.
1107 Here's a basic example:
1109 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1111 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1113 my $otp = $entry->time_otp;