File-KDBX

 view release on metacpan or  search on metacpan

lib/File/KDBX/Loader/V3.pm  view on Meta::CPAN

    }
    elsif ($type == HEADER_INNER_RANDOM_STREAM_ID) {
        ($val) = unpack('L<', $val);
    }
    elsif ($type == HEADER_KDF_PARAMETERS ||
           $type == HEADER_PUBLIC_CUSTOM_DATA) {
        throw "Unexpected KDBX4 header: $type", type => $type;
    }
    else {
        alert "Unknown header: $type", type => $type;
    }

    return wantarray ? ($type => $val, $buf) : $buf;
}

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,
        HEADER_INNER_RANDOM_STREAM_KEY,
        HEADER_STREAM_START_BYTES,
    ) {
        defined $kdbx->headers->{$field} or throw "Missing $field";
    }

    $kdbx->kdf_parameters({
        KDF_PARAM_UUID()        => KDF_UUID_AES,
        KDF_PARAM_AES_ROUNDS()  => delete $kdbx->headers->{+HEADER_TRANSFORM_ROUNDS},
        KDF_PARAM_AES_SEED()    => delete $kdbx->headers->{+HEADER_TRANSFORM_SEED},
    });

    my $master_seed = $kdbx->headers->{+HEADER_MASTER_SEED};

    my @cleanup;
    $key = $kdbx->composite_key($key);

    my $response = $key->challenge($master_seed);
    push @cleanup, erase_scoped $response;

    my $transformed_key = $kdbx->kdf->transform($key);
    push @cleanup, erase_scoped $transformed_key;

    my $final_key = digest_data('SHA256', $master_seed, $response, $transformed_key);
    push @cleanup, erase_scoped $final_key;

    my $cipher = $kdbx->cipher(key => $final_key);
    $fh = File::KDBX::IO::Crypt->new($fh, cipher => $cipher);

    read_all $fh, my $start_bytes, 32 or throw 'Failed to read starting bytes';

    my $expected_start_bytes = $kdbx->headers->{stream_start_bytes};
    $start_bytes eq $expected_start_bytes
        or throw "Invalid credentials or data is corrupt (wrong starting bytes)\n",
            got => $start_bytes, expected => $expected_start_bytes, headers => $kdbx->headers;

    $kdbx->key($key);

    $fh = File::KDBX::IO::HashBlock->new($fh);

    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_body($fh);
    close($fh);

    if (my $header_hash = $kdbx->meta->{header_hash}) {
        my $got_header_hash = digest_data('SHA256', $header_data);
        $header_hash eq $got_header_hash
            or throw 'Header hash does not match', got => $got_header_hash, expected => $header_hash;
    }
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

File::KDBX::Loader::V3 - Load KDBX3 files

=head1 VERSION

version 0.906

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website
L<https://github.com/chazmcgarvey/File-KDBX/issues>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

Charles McGarvey <ccm@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022 by Charles McGarvey.



( run in 0.578 second using v1.01-cache-2.11-cpan-39bf76dae61 )