1 package File
::KDBX
::Key
::File
;
7 use Crypt
::Digest
qw(digest_data);
8 use Crypt
::Misc
0.029 qw(decode_b64);
9 use File
::KDBX
::Constants
qw(:key_file);
10 use File
::KDBX
::Error
;
11 use File
::KDBX
::Util
qw(:erase trim);
12 use Ref
::Util
qw(is_ref is_scalarref);
13 use Scalar
::Util
qw(openhandle);
14 use XML
::LibXML
::Reader
;
17 use parent
'File::KDBX::Key';
19 our $VERSION = '999.999'; # VERSION
23 my $primitive = shift // throw
'Missing key primitive';
28 if (openhandle
($primitive)) {
29 seek $primitive, 0, 0; # not using ->seek method so it works on perl 5.10
30 my $buf = do { local $/; <$primitive> };
32 $cleanup = erase_scoped
$data;
34 elsif (is_scalarref
($primitive)) {
37 elsif (defined $primitive && !is_ref
($primitive)) {
38 open(my $fh, '<:raw', $primitive)
39 or throw
"Failed to open key file ($primitive)", filepath
=> $primitive;
40 my $buf = do { local $/; <$fh> };
42 $cleanup = erase_scoped
$data;
43 $self->{filepath
} = $primitive;
46 throw
'Unexpected primitive type', type
=> ref $primitive;
50 if (substr($$data, 0, 120) =~ /<KeyFile>/
51 and my ($type, $version) = $self->_load_xml($data, \
$raw_key)) {
52 $self->{type
} = $type;
53 $self->{version
} = $version;
54 $self->_set_raw_key($raw_key);
56 elsif (length($$data) == 32) {
57 $self->{type
} = KEY_FILE_TYPE_BINARY
;
58 $self->_set_raw_key($$data);
60 elsif ($$data =~ /^[A-Fa-f0-9]{64}$/) {
61 $self->{type
} = KEY_FILE_TYPE_HEX
;
62 $self->_set_raw_key(pack('H64', $$data));
65 $self->{type
} = KEY_FILE_TYPE_HASHED
;
66 $self->_set_raw_key(digest_data
('SHA256', $$data));
76 Re-read the key file
, if possible
, and update the raw key
if the key changed
.
82 $self->init($self->{filepath
}) if defined $self->{filepath
};
90 Get the type of key file
. Can be one of
:
93 * C<KEY_FILE_TYPE_BINARY>
94 * C<KEY_FILE_TYPE_HEX>
95 * C<KEY_FILE_TYPE_XML>
96 * C<KEY_FILE_TYPE_HASHED>
100 sub type
{ $_[0]->{type
} }
104 $version = $key->version;
106 Get the file version
. Only applies to XML key files
.
110 sub version
{ $_[0]->{version
} }
114 $filepath = $key->filepath;
116 Get the filepath to the key file
, if known
.
120 sub filepath
{ $_[0]->{filepath
} }
122 ##############################################################################
129 my ($version, $hash, $data);
131 my $reader = XML
::LibXML
::Reader-
>new(string
=> $$buf);
132 my $pattern = XML
::LibXML
::Pattern-
>new('/KeyFile/Meta/Version|/KeyFile/Key/Data');
134 while ($reader->nextPatternMatch($pattern) == 1) {
135 next if $reader->nodeType != XML_READER_TYPE_ELEMENT
;
136 my $name = $reader->localName;
137 if ($name eq 'Version') {
138 $reader->read if !$reader->isEmptyElement;
139 $reader->nodeType == XML_READER_TYPE_TEXT
140 or alert
'Expected text node with version', line
=> $reader->lineNumber;
141 my $val = trim
($reader->value);
143 and alert
'Overwriting version', previous
=> $version, new
=> $val, line
=> $reader->lineNumber;
146 elsif ($name eq 'Data') {
147 $hash = trim
($reader->getAttribute('Hash')) if $reader->hasAttributes;
148 $reader->read if !$reader->isEmptyElement;
149 $reader->nodeType == XML_READER_TYPE_TEXT
150 or alert
'Expected text node with data', line
=> $reader->lineNumber;
151 $data = $reader->value;
152 $data =~ s/\s+//g if defined $data;
156 return if !defined $version || !defined $data;
158 if ($version =~ /^1\.0/ && $data =~ /^[A-Za-z0-9+\/=]+$/) {
159 $$out = eval { decode_b64
($data) };
161 throw
'Failed to decode key in key file', version
=> $version, data
=> $data, error
=> $err;
163 return (KEY_FILE_TYPE_XML
, $version);
165 elsif ($version =~ /^2\.0/ && $data =~ /^[A-Fa-f0-9]+$/ && defined $hash && $hash =~ /^[A-Fa-f0-9]+$/) {
166 $$out = pack('H*', $data);
167 $hash = pack('H*', $hash);
168 my $got_hash = digest_data
('SHA256', $$out);
169 $hash eq substr($got_hash, 0, 4)
170 or throw
'Checksum mismatch', got
=> $got_hash, expected
=> $hash;
171 return (KEY_FILE_TYPE_XML
, $version);
174 throw
'Unexpected data in key file', version
=> $version, data
=> $data;