1 package File
::KDBX
::IO
::HmacBlock
;
2 # ABSTRACT: HMAC block stream IO handle
7 use Crypt
::Digest
qw(digest_data);
8 use Crypt
::Mac
::HMAC
qw(hmac);
10 use File
::KDBX
::Error
;
11 use File
::KDBX
::Util
qw(:class :int :io);
14 extends
'File::KDBX::IO';
16 our $VERSION = '999.999'; # VERSION
17 our $BLOCK_SIZE = 1048576; # 1MiB
22 Desired block size
when writing
(default: C
<$File::KDBX
::IO
::HmacBlock
::BLOCK_SIZE
> or 1,048,576 bytes
)
26 HMAC-SHA256 key
for authenticating the data stream
(required
)
31 _block_index
=> int64
(0),
32 _buffer
=> sub { \
(my $buf = '') },
34 block_size
=> sub { $BLOCK_SIZE },
37 while (my ($attr, $default) = each %ATTRS) {
38 no strict
'refs'; ## no critic (ProhibitNoStrict)
41 *$self->{$attr} = shift if @_;
42 *$self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
48 $fh = File
::KDBX
::IO
::HmacBlock-
>new(%attributes);
49 $fh = File
::KDBX
::IO
::HmacBlock-
>new($fh, %attributes);
51 Construct a new HMAC-block stream IO handle
.
57 my %args = @_ % 2 == 1 ? (fh
=> shift, @_) : @_;
58 my $self = $class->SUPER::new
;
59 $self->_fh($args{fh
}) or throw
'IO handle required';
60 $self->key($args{key
}) or throw
'Key required';
61 $self->block_size($args{block_size
});
69 $ENV{DEBUG_STREAM
} and print STDERR
"FILL\t$self\n";
70 return if $self->_finished;
72 my $block = eval { $self->_read_hashed_block($fh) };
74 $self->_set_error($err);
77 if (length($block) == 0) {
85 my ($self, $buf, $fh) = @_;
87 $ENV{DEBUG_STREAM
} and print STDERR
"WRITE\t$self ($fh)\n";
88 return 0 if $self->_finished;
90 ${*$self->{_buffer
}} .= $buf;
92 $self->_FLUSH($fh); # TODO only if autoflush?
100 $ENV{DEBUG_STREAM
} and print STDERR
"POPPED\t$self ($fh)\n";
101 return if $self->_mode ne 'w';
105 $self->_write_next_hmac_block($fh); # partial block with remaining content
106 $self->_write_final_hmac_block($fh); # terminating block
108 $self->_set_error($@) if $@;
112 my ($self, $fh) = @_;
114 $ENV{DEBUG_STREAM
} and print STDERR
"FLUSH\t$self ($fh)\n";
115 return if $self->_mode ne 'w';
118 while ($self->block_size <= length(${*$self->{_buffer
}})) {
119 $self->_write_next_hmac_block($fh);
123 $self->_set_error($err);
132 $ENV{DEBUG_STREAM
} and print STDERR
"err\t$self\n";
133 if (exists &Errno
::EPROTO
) {
136 elsif (exists &Errno
::EIO
) {
139 $self->_error($ERROR = error
(@_));
142 ##############################################################################
144 sub _read_hashed_block
{
148 read_all
$fh, my $hmac, 32 or throw
'Failed to read HMAC';
150 read_all
$fh, my $packed_size, 4 or throw
'Failed to read HMAC block size';
151 my ($size) = unpack('L<', $packed_size);
155 read_all
$fh, $block, $size
156 or throw
'Failed to read HMAC block', index => $self->_block_index, size
=> $size;
159 my $packed_index = pack_Ql
($self->_block_index);
160 my $got_hmac = hmac
('SHA256', $self->_hmac_key,
167 or throw
'Block authentication failed', index => $self->_block_index, got
=> $got_hmac, expected
=> $hmac;
169 *$self->{_block_index
}++;
173 sub _write_next_hmac_block
{
176 my $buffer = shift // $self->_buffer;
177 my $allow_empty = shift;
179 my $size = length($$buffer);
180 $size = $self->block_size if $self->block_size < $size;
181 return 0 if $size == 0 && !$allow_empty;
184 $block = substr($$buffer, 0, $size, '') if 0 < $size;
186 my $packed_index = pack_Ql
($self->_block_index);
187 my $packed_size = pack('L<', $size);
188 my $hmac = hmac
('SHA256', $self->_hmac_key,
194 $fh->print($hmac, $packed_size, $block)
195 or throw
'Failed to write HMAC block', hmac
=> $hmac, block_size
=> $size;
197 *$self->{_block_index
}++;
201 sub _write_final_hmac_block
{
205 $self->_write_next_hmac_block($fh, \'', 1);
210 my $key = shift // $self->key;
211 my $index = shift // $self->_block_index;
213 my $packed_index = pack_Ql($index);
214 my $hmac_key = digest_data('SHA512
', $packed_index, $key);
223 Writing to a HMAC-block stream handle will transform the data into a series of blocks. An HMAC is calculated
224 for each block and is included in the output.
226 Reading from a handle, each block will be verified and authenticated as the blocks are disassembled back into
229 This format helps ensure data integrity and authenticity of KDBX4 files.
231 Each block is encoded thusly:
234 * HMAC - 32 bytes, calculated over [block index (increments starting with 0), block size and data]
235 * Block size - Little-endian unsigned 32-bit (counting only the data)
236 * Data - String of bytes
238 The terminating block is an empty block encoded as usual but block size is 0 and there is no data.