]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Entry.pm
8bb30c4f9ffeb4dc6fae49e1b9a6507c171d7bc3
[chaz/p5-File-KDBX] / lib / File / KDBX / Entry.pm
1 package File::KDBX::Entry;
2 # ABSTRACT: A KDBX database entry
3
4 use warnings;
5 use strict;
6
7 use Crypt::Misc 0.029 qw(decode_b64 encode_b32r);
8 use Devel::GlobalDestruction;
9 use Encode qw(encode);
10 use File::KDBX::Constants qw(:history :icon);
11 use File::KDBX::Error;
12 use File::KDBX::Util qw(:assert :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(blessed looks_like_number);
17 use Storable qw(dclone);
18 use Time::Piece;
19 use boolean;
20 use namespace::clean;
21
22 extends 'File::KDBX::Object';
23
24 our $VERSION = '999.999'; # VERSION
25
26 my $PLACEHOLDER_MAX_DEPTH = 10;
27 my %PLACEHOLDERS;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
29
30 =attr uuid
31
32 128-bit UUID identifying the entry within the database.
33
34 =attr icon_id
35
36 Integer representing a default icon. See L<File::KDBX::Constants/":icon"> for valid values.
37
38 =attr custom_icon_uuid
39
40 128-bit UUID identifying a custom icon within the database.
41
42 =attr foreground_color
43
44 Text color represented as a string of the form C<#000000>.
45
46 =attr background_color
47
48 Background color represented as a string of the form C<#FFFFFF>.
49
50 =attr override_url
51
52 TODO
53
54 =attr tags
55
56 Text string with arbitrary tags which can be used to build a taxonomy.
57
58 =attr auto_type_enabled
59
60 Whether or not the entry is eligible to be matched for auto-typing.
61
62 =attr auto_type_obfuscation
63
64 Whether or not to use some kind of obfuscation when sending keystroke sequences to applications.
65
66 =attr auto_type_default_sequence
67
68 The default auto-type keystroke sequence.
69
70 =attr auto_type_associations
71
72 An array of window title / keystroke sequence associations.
73
74 {
75 window => 'Example Window Title',
76 keystroke_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
77 }
78
79 Keystroke sequences can have </Placeholders>, most commonly C<{USERNAME}> and C<{PASSWORD}>.
80
81 =attr previous_parent_group
82
83 128-bit UUID identifying a group within the database.
84
85 =attr quality_check
86
87 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
88
89 =attr strings
90
91 Hash with entry strings, including the standard strings as well as any custom ones.
92
93 {
94 # Every entry has these five strings:
95 Title => { value => 'Example Entry' },
96 UserName => { value => 'jdoe' },
97 Password => { value => 's3cr3t', protect => true },
98 URL => { value => 'https://example.com' }
99 Notes => { value => '' },
100 # May also have custom strings:
101 MySystem => { value => 'The mainframe' },
102 }
103
104 There are methods available to provide more convenient access to strings, including L</string>,
105 L</string_value>, L</expand_string_value> and L</string_peek>.
106
107 =attr binaries
108
109 Files or attachments. Binaries are similar to strings except they have a value of bytes instead of test
110 characters.
111
112 {
113 'myfile.txt' => {
114 value => '...',
115 },
116 'mysecrets.txt' => {
117 value => '...',
118 protect => true,
119 },
120 }
121
122 There are methods available to provide more convenient access to binaries, including L</binary> and
123 L</binary_value>.
124
125 =attr custom_data
126
127 A set of key-value pairs used to store arbitrary data, usually used by software to keep track of state rather
128 than by end users (who typically work with the strings and binaries).
129
130 =attr history
131
132 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
133 same UUID with the current entry.
134
135 =attr last_modification_time
136
137 Date and time when the entry was last modified.
138
139 =attr creation_time
140
141 Date and time when the entry was created.
142
143 =attr last_access_time
144
145 Date and time when the entry was last accessed.
146
147 =attr expiry_time
148
149 Date and time when the entry expired or will expire.
150
151 =attr expires
152
153 Boolean value indicating whether or not an entry is expired.
154
155 =attr usage_count
156
157 The number of times an entry has been used, which typically means how many times the B<Password> string has
158 been accessed.
159
160 =attr location_changed
161
162 Date and time when the entry was last moved to a different parent group.
163
164 =attr notes
165
166 Alias for the B<Notes> string value.
167
168 =attr password
169
170 Alias for the B<Password> string value.
171
172 =attr title
173
174 Alias for the B<Title> string value.
175
176 =attr url
177
178 Alias for the B<URL> string value.
179
180 =attr username
181
182 Aliases for the B<UserName> string value.
183
184 =cut
185
186 sub uuid {
187 my $self = shift;
188 if (@_ || !defined $self->{uuid}) {
189 my %args = @_ % 2 == 1 ? (uuid => shift, @_) : @_;
190 my $old_uuid = $self->{uuid};
191 my $uuid = $self->{uuid} = delete $args{uuid} // generate_uuid;
192 for my $entry (@{$self->history}) {
193 $entry->{uuid} = $uuid;
194 }
195 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
196 }
197 $self->{uuid};
198 }
199
200 # has uuid => sub { generate_uuid(printable => 1) };
201 has icon_id => ICON_PASSWORD, coerce => \&to_icon_constant;
202 has custom_icon_uuid => undef, coerce => \&to_uuid;
203 has foreground_color => '', coerce => \&to_string;
204 has background_color => '', coerce => \&to_string;
205 has override_url => '', coerce => \&to_string;
206 has tags => '', coerce => \&to_string;
207 has auto_type => {};
208 has previous_parent_group => undef, coerce => \&to_uuid;
209 has quality_check => true, coerce => \&to_bool;
210 has strings => {};
211 has binaries => {};
212 has times => {};
213 # has custom_data => {};
214 # has history => [];
215
216 has last_modification_time => sub { gmtime }, store => 'times', coerce => \&to_time;
217 has creation_time => sub { gmtime }, store => 'times', coerce => \&to_time;
218 has last_access_time => sub { gmtime }, store => 'times', coerce => \&to_time;
219 has expiry_time => sub { gmtime }, store => 'times', coerce => \&to_time;
220 has expires => false, store => 'times', coerce => \&to_bool;
221 has usage_count => 0, store => 'times', coerce => \&to_number;
222 has location_changed => sub { gmtime }, store => 'times', coerce => \&to_time;
223
224 # has 'auto_type.auto_type_enabled' => true, coerce => \&to_bool;
225 has 'auto_type_obfuscation' => 0, path => 'auto_type.data_transfer_obfuscation',
226 coerce => \&to_number;
227 has 'auto_type_default_sequence' => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
228 path => 'auto_type.default_sequence', coerce => \&to_string;
229 has 'auto_type_associations' => [], path => 'auto_type.associations';
230
231 my %ATTRS_STRINGS = (
232 title => 'Title',
233 username => 'UserName',
234 password => 'Password',
235 url => 'URL',
236 notes => 'Notes',
237 );
238 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
239 no strict 'refs'; ## no critic (ProhibitNoStrict)
240 *{$attr} = sub { shift->string_value($string_key, @_) };
241 *{"expand_${attr}"} = sub { shift->expand_string_value($string_key, @_) };
242 }
243
244 my @ATTRS = qw(uuid custom_data history auto_type_enabled);
245 sub _set_nonlazy_attributes {
246 my $self = shift;
247 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes(ref $self);
248 }
249
250 sub init {
251 my $self = shift;
252 my %args = @_;
253
254 while (my ($key, $val) = each %args) {
255 if (my $method = $self->can($key)) {
256 $self->$method($val);
257 }
258 else {
259 $self->string($key => $val);
260 }
261 }
262
263 return $self;
264 }
265
266 ##############################################################################
267
268 =method string
269
270 \%string = $entry->string($string_key);
271
272 $entry->string($string_key, \%string);
273 $entry->string($string_key, %attributes);
274 $entry->string($string_key, $value); # same as: value => $value
275
276 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
277 structure. For example:
278
279 $string = {
280 value => 'Password',
281 protect => true, # optional
282 };
283
284 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
285 which might exist:
286
287 =for :list
288 * C<protect> - Whether or not the string value should be memory-protected.
289
290 =cut
291
292 sub string {
293 my $self = shift;
294 my %args = @_ == 2 ? (key => shift, value => shift)
295 : @_ % 2 == 1 ? (key => shift, @_) : @_;
296
297 if (!defined $args{key} && !defined $args{value}) {
298 my %standard = (value => 1, protect => 1);
299 my @other_keys = grep { !$standard{$_} } keys %args;
300 if (@other_keys == 1) {
301 my $key = $args{key} = $other_keys[0];
302 $args{value} = delete $args{$key};
303 }
304 }
305
306 my $key = delete $args{key} or throw 'Must provide a string key to access';
307
308 return $self->{strings}{$key} = $args{value} if is_plain_hashref($args{value});
309
310 while (my ($field, $value) = each %args) {
311 $self->{strings}{$key}{$field} = $value;
312 }
313
314 # Auto-vivify the standard strings.
315 if ($STANDARD_STRINGS{$key}) {
316 return $self->{strings}{$key} //= {value => '', $self->_protect($key) ? (protect => true) : ()};
317 }
318 return $self->{strings}{$key};
319 }
320
321 ### Get whether or not a standard string is configured to be protected
322 sub _protect {
323 my $self = shift;
324 my $key = shift;
325 return false if !$STANDARD_STRINGS{$key};
326 if (my $kdbx = eval { $self->kdbx }) {
327 my $protect = $kdbx->memory_protection($key);
328 return $protect if defined $protect;
329 }
330 return $key eq 'Password';
331 }
332
333 =method string_value
334
335 $string = $entry->string_value($string_key);
336
337 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
338 is not set or is currently memory-protected. This is just a shortcut for:
339
340 my $string = do {
341 my $s = $entry->string(...);
342 defined $s ? $s->{value} : undef;
343 };
344
345 =cut
346
347 sub string_value {
348 my $self = shift;
349 my $string = $self->string(@_) // return undef;
350 return $string->{value};
351 }
352
353 =method expand_string_value
354
355 $string = $entry->expand_string_value;
356
357 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
358 do not expand to values are left as-is.
359
360 See L</Placeholders>.
361
362 Some placeholders (notably field references) require the entry be connected to a database and will throw an
363 error if it is not.
364
365 =cut
366
367 sub _expand_placeholder {
368 my $self = shift;
369 my $placeholder = shift;
370 my $arg = shift;
371
372 require File::KDBX;
373
374 my $placeholder_key = $placeholder;
375 if (defined $arg) {
376 $placeholder_key = $File::KDBX::PLACEHOLDERS{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
377 : "${placeholder}:";
378 }
379 return if !defined $File::KDBX::PLACEHOLDERS{$placeholder_key};
380
381 my $local_key = join('/', Hash::Util::FieldHash::id($self), $placeholder_key);
382 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
383 my $handler = $File::KDBX::PLACEHOLDERS{$placeholder_key} or next;
384 memoize recurse_limit($handler, $PLACEHOLDER_MAX_DEPTH, sub {
385 alert "Detected deep recursion while expanding $placeholder placeholder",
386 placeholder => $placeholder;
387 return; # undef
388 });
389 };
390
391 return $handler->($self, $arg, $placeholder);
392 }
393
394 sub _expand_string {
395 my $self = shift;
396 my $str = shift;
397
398 my $expand = memoize $self->can('_expand_placeholder'), $self;
399
400 # placeholders (including field references):
401 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
402
403 # environment variables (alt syntax):
404 my $vars = join('|', map { quotemeta($_) } keys %ENV);
405 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
406
407 return $str;
408 }
409
410 sub expand_string_value {
411 my $self = shift;
412 my $str = $self->string_peek(@_) // return undef;
413 my $cleanup = erase_scoped $str;
414 return $self->_expand_string($str);
415 }
416
417 =attr expand_notes
418
419 Shortcut equivalent to C<< ->expand_string_value('Notes') >>.
420
421 =attr expand_password
422
423 Shortcut equivalent to C<< ->expand_string_value('Password') >>.
424
425 =attr expand_title
426
427 Shortcut equivalent to C<< ->expand_string_value('Title') >>.
428
429 =attr expand_url
430
431 Shortcut equivalent to C<< ->expand_string_value('URL') >>.
432
433 =attr expand_username
434
435 Shortcut equivalent to C<< ->expand_string_value('UserName') >>.
436
437 =method other_strings
438
439 $other = $entry->other_strings;
440 $other = $entry->other_strings($delimiter);
441
442 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
443 for executing queries to search for entities based on the contents of these other strings (if any).
444
445 =cut
446
447 sub other_strings {
448 my $self = shift;
449 my $delim = shift // "\n";
450
451 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
452 return join($delim, @strings);
453 }
454
455 =method string_peek
456
457 $string = $entry->string_peek($string_key);
458
459 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
460 protected.
461
462 =cut
463
464 sub string_peek {
465 my $self = shift;
466 my $string = $self->string(@_);
467 return defined $string->{value} ? $string->{value} : $self->kdbx->peek($string);
468 }
469
470 ##############################################################################
471
472 =method add_auto_type_association
473
474 $entry->add_auto_type_association(\%association);
475
476 Add a new auto-type association to an entry.
477
478 =cut
479
480 sub add_auto_type_association {
481 my $self = shift;
482 my $association = shift;
483 push @{$self->auto_type_associations}, $association;
484 }
485
486 =method expand_keystroke_sequence
487
488 $string = $entry->expand_keystroke_sequence($keystroke_sequence);
489 $string = $entry->expand_keystroke_sequence(\%association);
490 $string = $entry->expand_keystroke_sequence; # use default auto-type sequence
491
492 Get a keystroke sequence after placeholder expansion.
493
494 =cut
495
496 sub expand_keystroke_sequence {
497 my $self = shift;
498 my $association = shift;
499
500 my $keys;
501 if ($association) {
502 $keys = is_hashref($association) && exists $association->{keystroke_sequence} ?
503 $association->{keystroke_sequence} : defined $association ? $association : '';
504 }
505
506 $keys = $self->auto_type_default_sequence if !$keys;
507 # TODO - Fall back to getting default sequence from parent group, which probably means we shouldn't be
508 # setting a default value in the entry..
509
510 return $self->_expand_string($keys);
511 }
512
513 ##############################################################################
514
515 =method binary
516
517 \%binary = $entry->binary($binary_key);
518
519 $entry->binary($binary_key, \%binary);
520 $entry->binary($binary_key, %attributes);
521 $entry->binary($binary_key, $value); # same as: value => $value
522
523 Get or set a binary. Every binary has a unique (to the entry) key and flags and so are returned as a hash
524 structure. For example:
525
526 $binary = {
527 value => '...',
528 protect => true, # optional
529 };
530
531 Every binary should have a value (but might be C<undef> due to memory protection) and these optional flags
532 which might exist:
533
534 =for :list
535 * C<protect> - Whether or not the binary value should be memory-protected.
536
537 =cut
538
539 sub binary {
540 my $self = shift;
541 my %args = @_ == 2 ? (key => shift, value => shift)
542 : @_ % 2 == 1 ? (key => shift, @_) : @_;
543
544 if (!defined $args{key} && !defined $args{value}) {
545 my %standard = (value => 1, protect => 1);
546 my @other_keys = grep { !$standard{$_} } keys %args;
547 if (@other_keys == 1) {
548 my $key = $args{key} = $other_keys[0];
549 $args{value} = delete $args{$key};
550 }
551 }
552
553 my $key = delete $args{key} or throw 'Must provide a binary key to access';
554
555 return $self->{binaries}{$key} = $args{value} if is_plain_hashref($args{value});
556
557 assert { !defined $args{value} || !utf8::is_utf8($args{value}) };
558 while (my ($field, $value) = each %args) {
559 $self->{binaries}{$key}{$field} = $value;
560 }
561 return $self->{binaries}{$key};
562 }
563
564 =method binary_value
565
566 $binary = $entry->binary_value($binary_key);
567
568 Access a binary value directly. The arguments are the same as for L</binary>. Returns C<undef> if the binary
569 is not set or is currently memory-protected. This is just a shortcut for:
570
571 my $binary = do {
572 my $b = $entry->binary(...);
573 defined $b ? $b->{value} : undef;
574 };
575
576 =cut
577
578 sub binary_value {
579 my $self = shift;
580 my $binary = $self->binary(@_) // return undef;
581 return $binary->{value};
582 }
583
584 ##############################################################################
585
586 =method hmac_otp
587
588 $otp = $entry->hmac_otp(%options);
589
590 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
591 strings generally must first be unprotected, just like when accessing the password. Valid options are:
592
593 =for :list
594 * C<counter> - Specify the counter value
595
596 To configure HOTP, see L</"One-time Passwords">.
597
598 =cut
599
600 sub hmac_otp {
601 my $self = shift;
602 load_optional('Pass::OTP');
603
604 my %params = ($self->_hotp_params, @_);
605 return if !defined $params{type} || !defined $params{secret};
606
607 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
608 $params{base32} = 1;
609
610 my $otp = eval {Pass::OTP::otp(%params, @_) };
611 if (my $err = $@) {
612 throw 'Unable to generate HOTP', error => $err;
613 }
614
615 $self->_hotp_increment_counter($params{counter});
616
617 return $otp;
618 }
619
620 =method time_otp
621
622 $otp = $entry->time_otp(%options);
623
624 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
625 strings generally must first be unprotected, just like when accessing the password. Valid options are:
626
627 =for :list
628 * C<now> - Specify the value for determining the time-step counter
629
630 To configure TOTP, see L</"One-time Passwords">.
631
632 =cut
633
634 sub time_otp {
635 my $self = shift;
636 load_optional('Pass::OTP');
637
638 my %params = ($self->_totp_params, @_);
639 return if !defined $params{type} || !defined $params{secret};
640
641 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
642 $params{base32} = 1;
643
644 my $otp = eval {Pass::OTP::otp(%params, @_) };
645 if (my $err = $@) {
646 throw 'Unable to generate TOTP', error => $err;
647 }
648
649 return $otp;
650 }
651
652 =method hmac_otp_uri
653
654 =method time_otp_uri
655
656 $uri_string = $entry->hmac_otp_uri;
657 $uri_string = $entry->time_otp_uri;
658
659 Get a HOTP or TOTP otpauth URI for the entry, if available.
660
661 To configure OTP, see L</"One-time Passwords">.
662
663 =cut
664
665 sub hmac_otp_uri { $_[0]->_otp_uri($_[0]->_hotp_params) }
666 sub time_otp_uri { $_[0]->_otp_uri($_[0]->_totp_params) }
667
668 sub _otp_uri {
669 my $self = shift;
670 my %params = @_;
671
672 return if 4 != grep { defined } @params{qw(type secret issuer account)};
673 return if $params{type} !~ /^[ht]otp$/i;
674
675 my $label = delete $params{label};
676 $params{$_} = uri_escape_utf8($params{$_}) for keys %params;
677
678 my $type = lc($params{type});
679 my $issuer = $params{issuer};
680 my $account = $params{account};
681
682 $label //= "$issuer:$account";
683
684 my $secret = $params{secret};
685 $secret = uc(encode_b32r($secret)) if !$params{base32};
686
687 delete $params{algorithm} if defined $params{algorithm} && $params{algorithm} eq 'sha1';
688 delete $params{period} if defined $params{period} && $params{period} == 30;
689 delete $params{digits} if defined $params{digits} && $params{digits} == 6;
690 delete $params{counter} if defined $params{counter} && $params{counter} == 0;
691
692 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
693
694 if (defined $params{encoder}) {
695 $uri .= "&encoder=$params{encoder}";
696 return $uri;
697 }
698 $uri .= '&algorithm=' . uc($params{algorithm}) if defined $params{algorithm};
699 $uri .= "&digits=$params{digits}" if defined $params{digits};
700 $uri .= "&counter=$params{counter}" if defined $params{counter};
701 $uri .= "&period=$params{period}" if defined $params{period};
702
703 return $uri;
704 }
705
706 sub _hotp_params {
707 my $self = shift;
708
709 my %params = (
710 type => 'hotp',
711 issuer => $self->title || 'KDBX',
712 account => $self->username || 'none',
713 digits => 6,
714 counter => $self->string_value('HmacOtp-Counter') // 0,
715 $self->_otp_secret_params('Hmac'),
716 );
717 return %params if $params{secret};
718
719 my %otp_params = $self->_otp_params;
720 return () if !$otp_params{secret} || $otp_params{type} ne 'hotp';
721
722 # $otp_params{counter} = 0
723
724 return (%params, %otp_params);
725 }
726
727 sub _totp_params {
728 my $self = shift;
729
730 my %algorithms = (
731 'HMAC-SHA-1' => 'sha1',
732 'HMAC-SHA-256' => 'sha256',
733 'HMAC-SHA-512' => 'sha512',
734 );
735 my %params = (
736 type => 'totp',
737 issuer => $self->title || 'KDBX',
738 account => $self->username || 'none',
739 digits => $self->string_value('TimeOtp-Length') // 6,
740 algorithm => $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
741 period => $self->string_value('TimeOtp-Period') // 30,
742 $self->_otp_secret_params('Time'),
743 );
744 return %params if $params{secret};
745
746 my %otp_params = $self->_otp_params;
747 return () if !$otp_params{secret} || $otp_params{type} ne 'totp';
748
749 return (%params, %otp_params);
750 }
751
752 # KeePassXC style
753 sub _otp_params {
754 my $self = shift;
755 load_optional('Pass::OTP::URI');
756
757 my $uri = $self->string_value('otp') || '';
758 my %params;
759 %params = Pass::OTP::URI::parse($uri) if $uri =~ m!^otpauth://!;
760 return () if !$params{secret} || !$params{type};
761
762 if (($params{encoder} // '') eq 'steam') {
763 $params{digits} = 5;
764 $params{chars} = '23456789BCDFGHJKMNPQRTVWXY';
765 }
766
767 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
768 my ($issuer, $user) = split(':', $params{label} // ':', 2);
769 $params{issuer} //= uri_unescape_utf8($issuer);
770 $params{account} //= uri_unescape_utf8($user);
771
772 $params{algorithm} = lc($params{algorithm}) if $params{algorithm};
773 $params{counter} = $self->string_value('HmacOtp-Counter') if $params{type} eq 'hotp';
774
775 return %params;
776 }
777
778 sub _otp_secret_params {
779 my $self = shift;
780 my $type = shift // return ();
781
782 my $secret_txt = $self->string_value("${type}Otp-Secret");
783 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
784 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
785 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
786
787 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
788 return () if $count == 0;
789 alert "Found multiple ${type}Otp-Secret strings", count => $count if 1 < $count;
790
791 return (secret => $secret_b32, base32 => 1) if defined $secret_b32;
792 return (secret => decode_b64($secret_b64)) if defined $secret_b64;
793 return (secret => pack('H*', $secret_hex)) if defined $secret_hex;
794 return (secret => encode('UTF-8', $secret_txt));
795 }
796
797 sub _hotp_increment_counter {
798 my $self = shift;
799 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
800
801 looks_like_number($counter) or throw 'HmacOtp-Counter value must be a number', value => $counter;
802 my $next = $counter + 1;
803 $self->string('HmacOtp-Counter', $next);
804 return $next;
805 }
806
807 ##############################################################################
808
809 =method size
810
811 $size = $entry->size;
812
813 Get the size (in bytes) of an entry.
814
815 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
816 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
817
818 =cut
819
820 sub size {
821 my $self = shift;
822
823 my $size = 0;
824
825 # tags
826 $size += length(encode('UTF-8', $self->tags // ''));
827
828 # attributes (strings)
829 while (my ($key, $string) = each %{$self->strings}) {
830 next if !defined $string->{value};
831 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $string->{value} // ''));
832 }
833
834 # custom data
835 while (my ($key, $item) = each %{$self->custom_data}) {
836 next if !defined $item->{value};
837 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $item->{value} // ''));
838 }
839
840 # binaries
841 while (my ($key, $binary) = each %{$self->binaries}) {
842 next if !defined $binary->{value};
843 my $value_len = utf8::is_utf8($binary->{value}) ? length(encode('UTF-8', $binary->{value}))
844 : length($binary->{value});
845 $size += length(encode('UTF-8', $key)) + $value_len;
846 }
847
848 # autotype associations
849 for my $association (@{$self->auto_type->{associations} || []}) {
850 $size += length(encode('UTF-8', $association->{window}))
851 + length(encode('UTF-8', $association->{keystroke_sequence} // ''));
852 }
853
854 return $size;
855 }
856
857 ##############################################################################
858
859 sub history {
860 my $self = shift;
861 my $entries = $self->{history} //= [];
862 if (@$entries && !blessed($entries->[0])) {
863 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
864 }
865 assert { !any { !blessed $_ } @$entries };
866 return $entries;
867 }
868
869 =method history_size
870
871 $size = $entry->history_size;
872
873 Get the size (in bytes) of all historical entries combined.
874
875 =cut
876
877 sub history_size {
878 my $self = shift;
879 return sum0 map { $_->size } @{$self->history};
880 }
881
882 =method prune_history
883
884 @removed_historical_entries = $entry->prune_history(%options);
885
886 Remove just as many older historical entries as necessary to get under the database limits. The limits are
887 taken from the connected database (if any) or can be overridden with C<%options>:
888
889 =for :list
890 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
891 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
892 * C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
893
894 =cut
895
896 sub prune_history {
897 my $self = shift;
898 my %args = @_;
899
900 my $max_items = $args{max_items} // eval { $self->kdbx->history_max_items } // HISTORY_DEFAULT_MAX_ITEMS;
901 my $max_size = $args{max_size} // eval { $self->kdbx->history_max_size } // HISTORY_DEFAULT_MAX_SIZE;
902 my $max_age = $args{max_age} // HISTORY_DEFAULT_MAX_AGE;
903
904 # history is ordered oldest to newest
905 my $history = $self->history;
906
907 my @removed;
908
909 if (0 <= $max_items && $max_items < @$history) {
910 push @removed, splice @$history, -$max_items;
911 }
912
913 if (0 <= $max_size) {
914 my $current_size = $self->history_size;
915 while ($max_size < $current_size) {
916 push @removed, my $entry = shift @$history;
917 $current_size -= $entry->size;
918 }
919 }
920
921 if (0 <= $max_age) {
922 my $cutoff = gmtime - ($max_age * 86400);
923 for (my $i = @$history - 1; 0 <= $i; --$i) {
924 my $entry = $history->[$i];
925 next if $cutoff <= $entry->last_modification_time;
926 push @removed, splice @$history, $i, 1;
927 }
928 }
929
930 @removed = sort { $a->last_modification_time <=> $b->last_modification_time } @removed;
931 return @removed;
932 }
933
934 =method add_historical_entry
935
936 $entry->add_historical_entry($entry);
937
938 Add an entry to the history.
939
940 =cut
941
942 sub add_historical_entry {
943 my $self = shift;
944 delete $_->{history} for @_;
945 push @{$self->{history} //= []}, map { $self->_wrap_entry($_) } @_;
946 }
947
948 =method remove_historical_entry
949
950 $entry->remove_historical_entry($historical_entry);
951
952 Remove an entry from the history.
953
954 =cut
955
956 sub remove_historical_entry {
957 my $self = shift;
958 my $entry = shift;
959 my $history = $self->history;
960
961 my @removed;
962 for (my $i = @$history - 1; 0 <= $i; --$i) {
963 my $item = $history->[$i];
964 next if Hash::Util::FieldHash::id($entry) != Hash::Util::FieldHash::id($item);
965 push @removed, splice @{$self->{history}}, $i, 1;
966 }
967 return @removed;
968 }
969
970 =method current_entry
971
972 $current_entry = $entry->current_entry;
973
974 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
975
976 =cut
977
978 sub current_entry {
979 my $self = shift;
980 my $parent = $self->group;
981
982 if ($parent) {
983 my $id = $self->uuid;
984 my $entry = first { $id eq $_->uuid } @{$parent->entries};
985 return $entry if $entry;
986 }
987
988 return $self;
989 }
990
991 =method is_current
992
993 $bool = $entry->is_current;
994
995 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
996 in the parent group's entry list.
997
998 =cut
999
1000 sub is_current {
1001 my $self = shift;
1002 my $current = $self->current_entry;
1003 return Hash::Util::FieldHash::id($self) == Hash::Util::FieldHash::id($current);
1004 }
1005
1006 =method is_historical
1007
1008 $bool = $entry->is_historical;
1009
1010 Get whether or not an entry is considered historical (i.e. not current).
1011
1012 This is just the inverse of L</is_current>.
1013
1014 =cut
1015
1016 sub is_historical { !$_[0]->is_current }
1017
1018 =method remove
1019
1020 $entry = $entry->remove;
1021
1022 Remove an entry from its parent group. If the entry is historical, remove it from the history of the current
1023 entry. If the entry is current, this behaves the same as L<File::KDBX::Object/remove>.
1024
1025 =cut
1026
1027 sub remove {
1028 my $self = shift;
1029 my $current = $self->current_entry;
1030 return $self if $current->remove_historical_entry($self);
1031 $self->SUPER::remove(@_);
1032 }
1033
1034 ##############################################################################
1035
1036 =method searching_enabled
1037
1038 $bool = $entry->searching_enabled;
1039
1040 Get whether or not an entry may show up in search results. This is determine from the entry's parent group's
1041 L<File::KDBX::Group/effective_enable_searching> value.
1042
1043 Throws if entry has no parent group or if the entry is not connected to a database.
1044
1045 =cut
1046
1047 sub searching_enabled {
1048 my $self = shift;
1049 my $parent = $self->group;
1050 return $parent->effective_enable_searching if $parent;
1051 return true;
1052 }
1053
1054 sub auto_type_enabled {
1055 my $self = shift;
1056 $self->auto_type->{enabled} = to_bool(shift) if @_;
1057 $self->auto_type->{enabled} //= true;
1058 return false if !$self->auto_type->{enabled};
1059 return true if !$self->is_connected;
1060 my $parent = $self->group;
1061 return $parent->effective_enable_auto_type if $parent;
1062 return true;
1063 }
1064
1065 ##############################################################################
1066
1067 sub _signal {
1068 my $self = shift;
1069 my $type = shift;
1070 return $self->SUPER::_signal("entry.$type", @_);
1071 }
1072
1073 sub _commit {
1074 my $self = shift;
1075 my $orig = shift;
1076 $self->add_historical_entry($orig);
1077 my $time = gmtime;
1078 $self->last_modification_time($time);
1079 $self->last_access_time($time);
1080 }
1081
1082 sub label { shift->expand_title(@_) }
1083
1084 ### Name of the parent attribute expected to contain the object
1085 sub _parent_container { 'entries' }
1086
1087 1;
1088 __END__
1089
1090 =for Pod::Coverage auto_type times
1091
1092 =head1 DESCRIPTION
1093
1094 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
1095 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
1096 that every entry has:
1097
1098 =for :list
1099 * B<Title>
1100 * B<UserName>
1101 * B<Password>
1102 * B<URL>
1103 * B<Notes>
1104
1105 Beyond this, you can store any number of other strings and any number of binaries that you can use for
1106 whatever purpose you want.
1107
1108 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
1109 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
1110 the attributes to see what's available.
1111
1112 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
1113
1114 =head2 Placeholders
1115
1116 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
1117 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
1118 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
1119 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
1120 C<http://example.com?user=batman>.
1121
1122 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
1123 brace, like C<{PLACEHOLDER:ARGUMENT}>.
1124
1125 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
1126 This software supports many (but not all) of the placeholders documented there.
1127
1128 =head3 Entry Placeholders
1129
1130 =for :list
1131 * ☑ C<{TITLE}> - B<Title> string
1132 * ☑ C<{USERNAME}> - B<UserName> string
1133 * ☑ C<{PASSWORD}> - B<Password> string
1134 * ☑ C<{NOTES}> - B<Notes> string
1135 * ☑ C<{URL}> - B<URL> string
1136 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
1137 * ☑ C<{URL:USERINFO}>
1138 * ☑ C<{URL:USERNAME}>
1139 * ☑ C<{URL:PASSWORD}>
1140 * ☑ C<{URL:HOST}>
1141 * ☑ C<{URL:PORT}>
1142 * ☑ C<{URL:PATH}>
1143 * ☑ C<{URL:QUERY}>
1144 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
1145 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
1146 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
1147 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
1148 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
1149 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
1150 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
1151 * ☑ C<{GROUP_PATH}> - Full path of the parent group
1152 * ☑ C<{GROUP}> - Name of the parent group
1153
1154 =head3 Field References
1155
1156 =for :list
1157 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
1158
1159 =head3 File path Placeholders
1160
1161 =for :list
1162 * ☑ C<{APPDIR}> - Program directory path
1163 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
1164 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
1165 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
1166 * ☑ C<{OPERA}> - Path to the Opera browser executable
1167 * ☑ C<{SAFARI}> - Path to the Safari browser executable
1168 * ☒ C<{DB_PATH}> - Full file path of the database
1169 * ☒ C<{DB_DIR}> - Directory path of the database
1170 * ☒ C<{DB_NAME}> - File name (including extension) of the database
1171 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
1172 * ☒ C<{DB_EXT}> - File name extension
1173 * ☑ C<{ENV_DIRSEP}> - Directory separator
1174 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
1175
1176 =head3 Date and Time Placeholders
1177
1178 =for :list
1179 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
1180 * ☑ C<{DT_YEAR}> - Year component of the current local date
1181 * ☑ C<{DT_MONTH}> - Month component of the current local date
1182 * ☑ C<{DT_DAY}> - Day component of the current local date
1183 * ☑ C<{DT_HOUR}> - Hour component of the current local time
1184 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
1185 * ☑ C<{DT_SECOND}> - Second component of the current local time
1186 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
1187 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
1188 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
1189 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
1190 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
1191 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
1192 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
1193
1194 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
1195
1196 =head3 Special Key Placeholders
1197
1198 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
1199 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
1200 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
1201
1202 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
1203 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
1204
1205 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
1206 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
1207
1208 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
1209 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
1210
1211 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
1212 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
1213
1214 =head3 Miscellaneous Placeholders
1215
1216 =for :list
1217 * ☒ C<{BASE}>
1218 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
1219 * ☒ C<{BASE:USERINFO}>
1220 * ☒ C<{BASE:USERNAME}>
1221 * ☒ C<{BASE:PASSWORD}>
1222 * ☒ C<{BASE:HOST}>
1223 * ☒ C<{BASE:PORT}>
1224 * ☒ C<{BASE:PATH}>
1225 * ☒ C<{BASE:QUERY}>
1226 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1227 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1228 * ☒ C<{CLIPBOARD-SET:/Text/}>
1229 * ☒ C<{CLIPBOARD}>
1230 * ☒ C<{CMD:/CommandLine/Options/}>
1231 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1232 * ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1233 * ☒ C<{GROUP_SEL_NOTES}>
1234 * ☒ C<{GROUP_SEL_PATH}>
1235 * ☒ C<{GROUP_SEL}>
1236 * ☒ C<{NEWPASSWORD}>
1237 * ☒ C<{NEWPASSWORD:/Profile/}>
1238 * ☒ C<{PASSWORD_ENC}>
1239 * ☒ C<{PICKCHARS}>
1240 * ☒ C<{PICKCHARS:Field:Options}>
1241 * ☒ C<{PICKFIELD}>
1242 * ☒ C<{T-CONV:/Text/Type/}>
1243 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1244
1245 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1246 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1247 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1248 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1249
1250 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1251 my ($entry) = @_;
1252 ...;
1253 };
1254
1255 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1256 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1257 strings or auto-complete key sequences.
1258
1259 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1260 my ($entry, $arg) = @_; # ^ Notice the colon here
1261 ...;
1262 };
1263
1264 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1265 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1266 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1267
1268 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1269 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1270 both with and without a colon (or they could be different subroutines):
1271
1272 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1273 (undef, my $arg) = @_;
1274 return defined $arg ? rand($arg) : rand;
1275 };
1276
1277 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1278 all the handlers:
1279
1280 %File::KDBX::PLACEHOLDERS = ();
1281
1282 =head2 One-time Passwords
1283
1284 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1285 configuration storage isn't completely standardized, but this module supports two predominant configuration
1286 styles:
1287
1288 =for :list
1289 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1290 * KeePassXC
1291
1292 B<NOTE:> To use this feature, you must install the suggested dependency:
1293
1294 =for :list
1295 * L<Pass::OTP>
1296
1297 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1298 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1299
1300 To configure TOTP in the KeePass 2 style, set the following strings:
1301
1302 =for :list
1303 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1304 C<HMAC-SHA-512>
1305 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1306 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1307 * C<TimeOtp-Secret> - Text string secret, OR
1308 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1309 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1310 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1311
1312 To configure HOTP in the KeePass 2 style, set the following strings:
1313
1314 =for :list
1315 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1316 is called
1317 * C<HmacOtp-Secret> - Text string secret, OR
1318 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1319 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1320 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1321
1322 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1323 these should actually be set or an error will be thrown.
1324
1325 Here's a basic example:
1326
1327 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1328 # OR
1329 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1330
1331 my $otp = $entry->time_otp;
1332
1333 =cut
This page took 0.122552 seconds and 3 git commands to generate.