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 :io assert_64bit);
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
)
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
.
59 my %args = @_ % 2 == 1 ? (fh
=> shift, @_) : @_;
60 my $self = $class->SUPER::new
;
61 $self->_fh($args{fh
}) or throw
'IO handle required';
62 $self->key($args{key
}) or throw
'Key required';
63 $self->block_size($args{block_size
});
71 $ENV{DEBUG_STREAM
} and print STDERR
"FILL\t$self\n";
72 return if $self->_finished;
74 my $block = eval { $self->_read_hashed_block($fh) };
76 $self->_set_error($err);
79 if (length($block) == 0) {
87 my ($self, $buf, $fh) = @_;
89 $ENV{DEBUG_STREAM
} and print STDERR
"WRITE\t$self ($fh)\n";
90 return 0 if $self->_finished;
92 ${*$self->{_buffer
}} .= $buf;
94 $self->_FLUSH($fh); # TODO only if autoflush?
100 my ($self, $fh) = @_;
102 $ENV{DEBUG_STREAM
} and print STDERR
"POPPED\t$self ($fh)\n";
103 return if $self->_mode ne 'w';
107 $self->_write_next_hmac_block($fh); # partial block with remaining content
108 $self->_write_final_hmac_block($fh); # terminating block
110 $self->_set_error($@) if $@;
114 my ($self, $fh) = @_;
116 $ENV{DEBUG_STREAM
} and print STDERR
"FLUSH\t$self ($fh)\n";
117 return if $self->_mode ne 'w';
120 while ($self->block_size <= length(${*$self->{_buffer
}})) {
121 $self->_write_next_hmac_block($fh);
125 $self->_set_error($err);
134 $ENV{DEBUG_STREAM
} and print STDERR
"err\t$self\n";
135 if (exists &Errno
::EPROTO
) {
138 elsif (exists &Errno
::EIO
) {
141 $self->_error($ERROR = error
(@_));
144 ##############################################################################
146 sub _read_hashed_block
{
150 read_all
$fh, my $hmac, 32 or throw
'Failed to read HMAC';
152 read_all
$fh, my $packed_size, 4 or throw
'Failed to read HMAC block size';
153 my ($size) = unpack('L<', $packed_size);
157 read_all
$fh, $block, $size
158 or throw
'Failed to read HMAC block', index => $self->_block_index, size
=> $size;
161 my $packed_index = pack('Q<', $self->_block_index);
162 my $got_hmac = hmac
('SHA256', $self->_hmac_key,
169 or throw
'Block authentication failed', index => $self->_block_index, got
=> $got_hmac, expected
=> $hmac;
171 *$self->{_block_index
}++;
175 sub _write_next_hmac_block
{
178 my $buffer = shift // $self->_buffer;
179 my $allow_empty = shift;
181 my $size = length($$buffer);
182 $size = $self->block_size if $self->block_size < $size;
183 return 0 if $size == 0 && !$allow_empty;
186 $block = substr($$buffer, 0, $size, '') if 0 < $size;
188 my $packed_index = pack('Q<', $self->_block_index);
189 my $packed_size = pack('L<', $size);
190 my $hmac = hmac
('SHA256', $self->_hmac_key,
196 $fh->print($hmac, $packed_size, $block)
197 or throw
'Failed to write HMAC block', hmac
=> $hmac, block_size
=> $size;
199 *$self->{_block_index
}++;
203 sub _write_final_hmac_block
{
207 $self->_write_next_hmac_block($fh, \'', 1);
212 my $key = shift // $self->key;
213 my $index = shift // $self->_block_index;
215 my $packed_index = pack('Q
<', $index);
216 my $hmac_key = digest_data('SHA512
', $packed_index, $key);
225 Writing to a HMAC-block stream handle will transform the data into a series of blocks. An HMAC is calculated
226 for each block and is included in the output.
228 Reading from a handle, each block will be verified and authenticated as the blocks are disassembled back into
231 This format helps ensure data integrity and authenticity of KDBX4 files.
233 Each block is encoded thusly:
236 * HMAC - 32 bytes, calculated over [block index (increments starting with 0), block size and data]
237 * Block size - Little-endian unsigned 32-bit (counting only the data)
238 * Data - String of bytes
240 The terminating block is an empty block encoded as usual but block size is 0 and there is no data.