File-KDBX
view release on metacpan or search on metacpan
lib/File/KDBX/Loader/V4.pm view on Meta::CPAN
}
elsif ($type == VMAP_TYPE_INT32) {
($val) = unpack('l<', $val);
}
elsif ($type == VMAP_TYPE_INT64) {
($val) = unpack_ql($val);
}
elsif ($type == VMAP_TYPE_STRING) {
$val = decode('UTF-8', $val);
}
elsif ($type == VMAP_TYPE_BYTEARRAY) {
# nothing
}
else {
throw 'Unknown variant type', type => $type;
}
$dict{$key} = $val;
}
return \%dict;
}
sub _read_body {
my $self = shift;
my $fh = shift;
my $key = shift;
my $header_data = shift;
my $kdbx = $self->kdbx;
# assert all required headers present
for my $field (
HEADER_CIPHER_ID,
HEADER_ENCRYPTION_IV,
HEADER_MASTER_SEED,
) {
defined $kdbx->headers->{$field} or throw "Missing $field";
}
my @cleanup;
# checksum check
read_all $fh, my $header_hash, 32 or throw 'Failed to read header hash';
my $got_header_hash = digest_data('SHA256', $header_data);
$got_header_hash eq $header_hash
or throw 'Data is corrupt (header checksum mismatch)',
got => $got_header_hash, expected => $header_hash;
$key = $kdbx->composite_key($key);
my $transformed_key = $kdbx->kdf->transform($key);
push @cleanup, erase_scoped $transformed_key;
# authentication check
read_all $fh, my $header_hmac, 32 or throw 'Failed to read header HMAC';
my $hmac_key = digest_data('SHA512', $kdbx->headers->{master_seed}, $transformed_key, "\x01");
push @cleanup, erase_scoped $hmac_key;
my $got_header_hmac = hmac('SHA256',
digest_data('SHA512', "\xff\xff\xff\xff\xff\xff\xff\xff", $hmac_key),
$header_data,
);
$got_header_hmac eq $header_hmac
or throw "Invalid credentials or data is corrupt (header HMAC mismatch)\n",
got => $got_header_hmac, expected => $header_hmac;
$kdbx->key($key);
$fh = File::KDBX::IO::HmacBlock->new($fh, key => $hmac_key);
my $final_key = digest_data('SHA256', $kdbx->headers->{master_seed}, $transformed_key);
push @cleanup, erase_scoped $final_key;
my $cipher = $kdbx->cipher(key => $final_key);
$fh = File::KDBX::IO::Crypt->new($fh, cipher => $cipher);
my $compress = $kdbx->headers->{+HEADER_COMPRESSION_FLAGS};
if ($compress == COMPRESSION_GZIP) {
load_optional('IO::Uncompress::Gunzip');
$fh = IO::Uncompress::Gunzip->new($fh)
or throw "Failed to initialize compression library: $IO::Uncompress::Gunzip::GunzipError",
error => $IO::Uncompress::Gunzip::GunzipError;
}
elsif ($compress != COMPRESSION_NONE) {
throw "Unsupported compression ($compress)\n", compression_flags => $compress;
}
$self->_read_inner_headers($fh);
$self->_read_inner_body($fh);
}
sub _read_inner_headers {
my $self = shift;
my $fh = shift;
while (my ($type, $val) = $self->_read_inner_header($fh)) {
last if $type == INNER_HEADER_END;
}
}
sub _read_inner_header {
my $self = shift;
my $fh = shift;
my $kdbx = $self->kdbx;
read_all $fh, my $buf, 5 or throw 'Expected inner header type and size';
my ($type, $size) = unpack('C L<', $buf);
my $val;
if (0 < $size) {
read_all $fh, $val, $size or throw 'Expected inner header value', type => $type, size => $size;
}
$type = to_inner_header_constant($type) // $type;
if ($type == INNER_HEADER_END) {
# nothing
}
elsif ($type == INNER_HEADER_INNER_RANDOM_STREAM_ID) {
$val = unpack('L<', $val);
$kdbx->inner_headers->{$type} = $val;
}
elsif ($type == INNER_HEADER_INNER_RANDOM_STREAM_KEY) {
$kdbx->inner_headers->{$type} = $val;
}
( run in 0.872 second using v1.01-cache-2.11-cpan-39bf76dae61 )