File Coverage

blib/lib/File/KDBX/Cipher.pm
Criterion Covered Total %
statement 82 102 80.3
branch 17 36 47.2
condition 5 16 31.2
subroutine 20 30 66.6
pod 19 19 100.0
total 143 203 70.4


line stmt bran cond sub pod time code
1             package File::KDBX::Cipher;
2             # ABSTRACT: A block cipher mode or cipher stream
3              
4 9     9   96218 use warnings;
  9         19  
  9         256  
5 9     9   39 use strict;
  9         19  
  9         146  
6              
7 9     9   384 use Devel::GlobalDestruction;
  9         535  
  9         45  
8 9     9   507 use File::KDBX::Constants qw(:cipher :random_stream);
  9         16  
  9         1179  
9 9     9   55 use File::KDBX::Error;
  9         13  
  9         423  
10 9     9   70 use File::KDBX::Util qw(:class erase format_uuid);
  9         18  
  9         902  
11 9     9   52 use Module::Load;
  9         24  
  9         64  
12 9     9   450 use Scalar::Util qw(looks_like_number);
  9         16  
  9         381  
13 9     9   84 use namespace::clean;
  9         28  
  9         64  
14              
15             our $VERSION = '0.906'; # VERSION
16              
17 0 0   0 1 0 my %CIPHERS;
18 0 0   0 1 0  
19 0 50 0 240 1 0  
  240         812  
20 0 50 0 240 1 0 has 'uuid', is => 'ro';
  240         569  
21 240   50     822 has 'stream_id', is => 'ro';
22 240   50     1628 has 'key', is => 'ro';
23             has 'iv', is => 'ro';
24 0     0 1 0 sub iv_size { 0 }
25 0     0 1 0 sub key_size { -1 }
26 0     0 1 0 sub block_size { 0 }
27 240 50   240 1 1218 sub algorithm { $_[0]->{algorithm} or throw 'Block cipher algorithm is not set' }
28              
29              
30             sub new {
31 150     150 1 8902 my $class = shift;
32 150         432 my %args = @_;
33              
34 150 100       487 return $class->new_from_uuid(delete $args{uuid}, %args) if defined $args{uuid};
35 102 50       462 return $class->new_from_stream_id(delete $args{stream_id}, %args) if defined $args{stream_id};
36              
37 0         0 throw 'Must pass uuid or stream_id';
38             }
39              
40             sub new_from_uuid {
41 48     48 1 80 my $class = shift;
42 48         79 my $uuid = shift;
43 48         98 my %args = @_;
44              
45 48 50       106 $args{key} or throw 'Missing encryption key';
46 48 50       98 $args{iv} or throw 'Missing encryption IV';
47              
48 48         124 my $formatted_uuid = format_uuid($uuid);
49              
50 48 50       148 my $cipher = $CIPHERS{$uuid} or throw "Unsupported cipher ($formatted_uuid)", uuid => $uuid;
51 48         184 ($class, my %registration_args) = @$cipher;
52              
53 48         173 my @args = (%args, %registration_args, uuid => $uuid);
54 48         167 load $class;
55 48         2601 my $self = bless {@args}, $class;
56 48         178 return $self->init(@args);
57             }
58              
59             sub new_from_stream_id {
60 102     102 1 154 my $class = shift;
61 102         140 my $id = shift;
62 102         190 my %args = @_;
63              
64 102 50       227 $args{key} or throw 'Missing encryption key';
65              
66 102 50       266 my $cipher = $CIPHERS{$id} or throw "Unsupported stream cipher ($id)", id => $id;
67 102         260 ($class, my %registration_args) = @$cipher;
68              
69 102         315 my @args = (%args, %registration_args, stream_id => $id);
70 102         320 load $class;
71 102         5417 my $self = bless {@args}, $class;
72 102         359 return $self->init(@args);
73             }
74              
75              
76 47     47 1 263 sub init { $_[0] }
77              
78 241 50   241   4834 sub DESTROY { !in_global_destruction and erase \$_[0]->{key} }
79              
80              
81 0     0 1 0 sub encrypt { die 'Not implemented' }
82              
83              
84 0     0 1 0 sub decrypt { die 'Not implemented' }
85              
86              
87 0     0 1 0 sub finish { '' }
88              
89              
90             sub encrypt_finish {
91 2     2 1 1609 my $self = shift;
92 2         7 my $out = $self->encrypt(@_);
93 2         6 $out .= $self->finish;
94 2         6 return $out;
95             }
96              
97              
98             sub decrypt_finish {
99 0     0 1 0 my $self = shift;
100 0         0 my $out = $self->decrypt(@_);
101 0         0 $out .= $self->finish;
102 0         0 return $out;
103             }
104              
105              
106             sub register {
107 72     72 1 100 my $class = shift;
108 72         88 my $id = shift;
109 72         94 my $package = shift;
110 72         126 my @args = @_;
111              
112 72 100       251 my $formatted_id = looks_like_number($id) ? $id : format_uuid($id);
113 72 50 33     470 $package = "${class}::${package}" if $package !~ s/^\+// && $package !~ /^\Q${class}::\E/;
114              
115 0 0       0 my %blacklist = map { (looks_like_number($_) ? $_ : File::KDBX::Util::uuid($_)) => 1 }
116 72   50     243 split(/,/, $ENV{FILE_KDBX_CIPHER_BLACKLIST} // '');
117 72 50 33     226 if ($blacklist{$id} || $blacklist{$package}) {
118 0         0 alert "Ignoring blacklisted cipher ($formatted_id)", id => $id, package => $package;
119 0         0 return;
120             }
121              
122 72 50       140 if (defined $CIPHERS{$id}) {
123 0         0 alert "Overriding already-registered cipher ($formatted_id) with package $package",
124             id => $id,
125             package => $package;
126             }
127              
128 72         454 $CIPHERS{$id} = [$package, @args];
129             }
130              
131              
132             sub unregister {
133 0     0 1   delete $CIPHERS{$_} for @_;
134             }
135              
136             BEGIN {
137 9     9   11819 __PACKAGE__->register(CIPHER_UUID_AES128, 'CBC', algorithm => 'AES', key_size => 16);
138 9         35 __PACKAGE__->register(CIPHER_UUID_AES256, 'CBC', algorithm => 'AES', key_size => 32);
139 9         31 __PACKAGE__->register(CIPHER_UUID_SERPENT, 'CBC', algorithm => 'Serpent', key_size => 32);
140 9         29 __PACKAGE__->register(CIPHER_UUID_TWOFISH, 'CBC', algorithm => 'Twofish', key_size => 32);
141 9         27 __PACKAGE__->register(CIPHER_UUID_CHACHA20, 'Stream', algorithm => 'ChaCha');
142 9         27 __PACKAGE__->register(CIPHER_UUID_SALSA20, 'Stream', algorithm => 'Salsa20');
143 9         30 __PACKAGE__->register(STREAM_ID_CHACHA20, 'Stream', algorithm => 'ChaCha');
144 9         22 __PACKAGE__->register(STREAM_ID_SALSA20, 'Stream', algorithm => 'Salsa20');
145             }
146              
147             1;
148              
149             __END__