File Coverage

blib/lib/Crypt/NamedKeys.pm
Criterion Covered Total %
statement 81 82 98.7
branch 22 32 68.7
condition 8 11 72.7
subroutine 18 18 100.0
pod 6 6 100.0
total 135 149 90.6


line stmt bran cond sub pod time code
1             package Crypt::NamedKeys;
2              
3 2     2   89035 use Moo;
  2         22679  
  2         10  
4              
5             =head1 NAME
6              
7             Crypt::NamedKeys - A Crypt::CBC wrapper with key rotation support
8              
9             =head1 SYNOPSYS
10              
11             use Crypt::NamedKeys;
12             my $crypt = Crypt::NamedKeys->new(keyname => 'href');
13             my $encrypted = $crypt->encrypt_data(data => $href);
14             my $restored_href = $crypt->decrypt_data(
15             data => $encrypted->{data},
16             mac => $encrypted->{mac},
17             );
18              
19             =head1 DESCRIPTION
20              
21             This module provides functions to serialize data for transfer via non-protected
22             channels with encryption and data integrity protection. The module tracks key
23             number used to encrypt information so that keys can be rotated without making
24             data unreadable.
25              
26             =head1 CONFIGURATION AND KEY ROTATION
27              
28             The keys are stored in the keyfile, configurable as below. Keys are numbered
29             starting at 1. Numbers must never be reused. Typically key rotation will be
30             done in several steps, each with its own rollout. These steps MUST be done as
31             separate releases because otherwise keys may not be available to decrypt data,
32             and so things may not work.
33              
34             =head2 keyfile location
35              
36             The keyfile can be set using the keyfile($path) function. There is no default.
37              
38             =head2 keyfile format
39              
40             The format of the keyfile is YAML, following a basic structure of
41              
42             keyname:
43             [keyhashdef]
44              
45             so for example:
46              
47             cryptedfeed:
48             default_keynum: 9
49             none: queith7eeTh0teejaichoodobooX9ceechee9Sai9gauChiengaeraew3aDiehei
50             1: aePh8ahBaNg1bee6ohj3er5cuzeepoophai1oogohpoixothah4AuYiongu4ahta
51             2: oht1eep8uxoo1eeshaSaemee9aem5chahqueu0Aedaa7eeXae9aeghe5umoNah6a
52             3: chigh4veifoofe0Vohphee4ohkaef9giz2iaje2ahF4ohboSh6ifaiNgohwohchi
53             4: Ahphahmisaingo5Ietheangeegi5ia1uuF9taerooShaitoh1Eophig3ohziejet
54             5: oe5wi2equee6FeiZohjah2peas6Ahquohniefeimai0beip2waxeizoo1OhthohN
55             6: eigaezee3CeuC8phae4giph6Miqu6piy3Eideipahticesheij7se9eecai9fiez
56             7: DuuGhohViGh0Sheihahr6ce4Phuin7ahpaiSa5jaiphie3eiz8oa3dohrohghuow
57             8: ahfoniemah4boemeN8seJ7hohhualeetei7aegohhai5ohwahlohnah2Ee2Ewal1
58             9: Ceixei4shelohxee1ohdoochuliebael1kae8eit0Geeth1so9fohZi0cohs8go4
59             10: boreiDe0shueNgie7shai7ooc1yaeveiKeihuox0xahp1hai8phe7aephiel2oob
60              
61             In general we assume key spefications to use numeric keys within the named
62             key hash. This makes key rotation a lot easier and prevents reusing key
63             numbers.
64              
65             Key names may not contain = or -.
66              
67             All keys listed can be used for decryption (with the special 'none' key used if
68             no key number is specified in the cyphertex), but by default only the default
69             keynumber (default_keynum, in this case 9) is used for encrypting.
70              
71             The keynumber is specified in the resulting cyphertext so we know which key
72             to use for decrypting the cyphertext even if we don't try to decrypt it. This
73             allows:
74              
75             =over
76              
77             =item Key checking
78              
79             If you store cyphertext in your rdbms, you can check which keys are used before
80             you remove decryption support for a key.
81              
82             =item Orderly key rotation
83              
84             You can add a key, and later depricate it, managing the transition (and perhaps
85             even using logging to know when the old key is no longer needed).
86              
87             =back
88              
89             =head2 Step 1: Adding a New Key
90              
91             In many cases you need to be able to add and remove keys without requiring that
92             everything gets the new keys at the same time. For example if you have multiple
93             production systems, they are likely to get updated in series, and if you expect
94             that everyone gets the keys at the same time, timing issues may occur.
95              
96             For this reason, we recommend breaking up the encryption key rollout into a
97             number of steps. The first one is making sure that everyone can use the
98             new key to decrypt before anyone uses it to encrypt.
99              
100             The first release is by adding a new key so that it is available for decryption.
101              
102             For example, in the keyfile suppose one has:
103              
104             mykey:
105             default_keynum: 1
106             none: rsdfagtiaueIUPOIUYHH
107             1: rsdfagtiaueIUPOIUYHH
108              
109             We might add another line
110              
111             2: IRvswqerituq-HPIOHJHGdeewrwyugfrGRSe3eyy6te
112              
113             Once this file is released, the key number 2 will be available globally for
114             decryption purposes, but everything will still be encrypted using key number 1.
115              
116             This means it is safe then to go onto the second step.
117              
118             =head2 Step 2: Setting the new key as default
119              
120             Once the new keys have been released, the next step is to change the default
121             keynumber. Data encrypted in this way will be available even to servers waiting
122             to be updated because the keys have previously been rolled out. To do this,
123             simply change the default_keynum:
124              
125             mykey:
126             default_keynum: 1
127             1: rsdfagtiaueIUPOIUYHH
128             2: IRvswqerituq-HPIOHJHGdeewrwyugfrGRSe3eyy6te
129              
130             becomes:
131              
132             mykey:
133             default_keynum: 2
134             1: rsdfagtiaueIUPOIUYHH
135             2: IRvswqerituq-HPIOHJHGdeewrwyugfrGRSe3eyy6te
136              
137             Now all new data will be encrypted using keynumber 2.
138              
139             =head2 Step 3: Retiring the old key
140              
141             Once the old key is no longer being used, it can be retired by deleting the
142             row.
143              
144             =head2 The Special 'none' keynum
145              
146             For aes keys before the key versioning was introduced, there is no keynum
147             associated with the cyphertext, so we use this key.
148              
149             =cut
150              
151 2     2   2469 use Carp;
  2         3  
  2         111  
152 2     2   1071 use Crypt::CBC;
  2         7195  
  2         62  
153 2     2   1897 use Digest::SHA qw(hmac_sha256_base64 sha256);
  2         5323  
  2         163  
154 2     2   1217 use JSON;
  2         20321  
  2         11  
155 2     2   1239 use MIME::Base64;
  2         1013  
  2         113  
156 2     2   941 use String::Compare::ConstantTime;
  2         808  
  2         90  
157 2     2   871 use Try::Tiny;
  2         2081  
  2         99  
158 2     2   795 use YAML::XS;
  2         3969  
  2         1910  
159              
160              
161             our $VERSION = '1.1.0';
162              
163             =head1 CONFIGURATION PARAMETERS
164              
165             =head2 $Crypt::NamedKeys::Escape_Eq;
166              
167             Set to true, using local or not, if you want to encode with - instead of =
168              
169             Note that on decryption both are handled.
170              
171             =cut
172              
173             our $Escape_Eq = 0;
174              
175             =head1 PROPERTIES
176              
177             =head2 keynum
178              
179             Defaults to the default keynumber specified in the keyfile (for encryption)
180              
181             =cut
182              
183             has keynum => (
184             is => 'ro',
185             lazy => 1,
186             builder => '_default_keynum',
187             );
188              
189             =head2 keyname
190              
191             The name of the key in the keyfile.
192              
193             =cut
194              
195             has keyname => (
196             is => 'ro',
197             required => 1,
198             );
199              
200             my $keyfile;
201              
202             =head1 METHODS AND FUNCTIONS
203              
204             =cut
205             =head2 Crypt::NamedKeys->keyfile($path)
206              
207             Can also be called as Crypt::NamedKeys::keyfile($path)
208              
209             Sets the path of the keyfile. It does not load or reload it (that is done on
210             demand or by reload_keyfile() below
211              
212             =cut
213              
214             sub keyfile {
215 1     1 1 2193 my $file = shift;
216 1 50       5 $file = shift if $file eq __PACKAGE__;
217 1 50       4 return $keyfile unless $file;
218 1         2 $keyfile = $file;
219             }
220              
221             my $keyhash;
222              
223             my $get_keyhash = sub {
224             return $keyhash if $keyhash;
225             reload_keyhash();
226             return $keyhash;
227             };
228              
229             =head2 reload_keyhash
230              
231             Can be called as an object method or function (i.e.
232             Crypt::NamedKeys::reload_keyhash()
233              
234             Loads or reloads the keyfile. Can be used via event handlers to reload
235             confguration as needed
236              
237             =cut
238              
239             sub reload_keyhash {
240 1 50   1 1 3 croak 'No keyfile defined (use keyfile() to set)' unless $keyfile;
241 1         4 $keyhash = YAML::XS::LoadFile($keyfile);
242 1         176 return scalar keys %$keyhash;
243             }
244              
245             my $get_secret = sub {
246             my %args = @_;
247             croak 'No key name specified' unless $args{keyname};
248             croak 'No key number specified' unless $args{keynum};
249             my $keytab = &$get_keyhash()->{$args{keyname}};
250             return $keytab->{$args{keynum}};
251             };
252              
253             sub _default_keynum {
254 2     2   344 my $self = shift;
255 2         7 my $keytab = &$get_keyhash()->{$self->keyname};
256 2 50       8 warn 'No default key found for ' . $self->keyname
257             unless $keytab->{default_keynum};
258 2         10 return $keytab->{default_keynum};
259             }
260              
261             my $mac_secret = sub {
262             my %args = @_;
263             return sha256(&$get_secret(@_)); ## nocritic
264             };
265              
266             =head2 $self->encrypt_data(data => $data)
267              
268             Serialize I<$data> to JSON, encrypt it, and encode as base64. Also compute HMAC
269             code for the encrypted data. Returns hash reference with 'data' and 'mac'
270             elements.
271              
272             Args include
273              
274             =over
275              
276             =item data
277              
278             Data structure reference to be encrypted
279              
280             =item cypher
281              
282             Cypher to use (default: Rijndael)
283              
284             =back
285              
286             =cut
287              
288             sub encrypt_data {
289 6     6 1 3449 my ($self, %args) = @_;
290 6 100 100     73 croak "data argument is required and must be a reference" unless $args{data} and ref $args{data};
291 4         52 my $json_data = encode_json($args{data});
292 3   50     14 my $cypher = $args{cypher} || 'Rijndael';
293             # Crypt::CBC generates random 8 bytes salt that it uses to
294             # derive IV and encryption key from $args{secret}. It uses
295             # the same algorythm as OpenSSL, the output is identical to
296             # openssl enc -e -aes-256-cbc -k $args{secret} -salt
297 3         63 my $cbc = Crypt::CBC->new(
298             -key => &$get_secret(
299             keyname => $self->keyname,
300             keynum => $self->keynum,
301             ),
302             -cipher => $cypher,
303             -salt => 1,
304             );
305 3         1293 my $data = encode_base64($cbc->encrypt($json_data), '');
306 3         3132 my $mac = hmac_sha256_base64(
307             $data,
308             &$mac_secret(
309             keyname => $self->keyname,
310             keynum => $self->keynum
311             ));
312 3 50       10 $data =~ s/=/-/g if $Escape_Eq;
313 3 50       8 $mac =~ s/=/-/g if $Escape_Eq;
314             return {
315 3         55 data => $self->keynum . '*' . $data,
316             mac => $mac,
317             };
318             }
319              
320             =head2 $self->decrypt_data(data => $data, mac => $mac)
321              
322             Decrypt data encrypted using I. First checks HMAC code for data.
323             If data was not tampered, decrypts it and decodes from JSON. Returns data, or
324             undef if decryption failed.
325              
326             =cut
327              
328             sub decrypt_data {
329 12     12 1 7536 my ($self, %args) = @_;
330 12 100 100     79 croak "method requires data and mac arguments" unless $args{data} and $args{mac};
331             # if the data was tampered do not try to decrypt it
332              
333 9         15 $args{data} =~ s/-/=/g;
334 9         15 $args{mac} =~ s/-/=/g;
335 9         31 my ($keynum, $cyphertext) = split /\*/, $args{data}, 2;
336              
337 9 100       19 if (!$cyphertext) {
338 1         1 $cyphertext = $keynum;
339 1         3 $keynum = 'none';
340             }
341 9         28 my $secret = &$get_secret(
342             keynum => $keynum,
343             keyname => $self->keyname
344             );
345 9 50 33     35 return unless ($cyphertext and $secret);
346 9         19 my $msg_mac = hmac_sha256_base64(
347             $cyphertext,
348             &$mac_secret(
349             keynum => $keynum,
350             keyname => $self->keyname,
351             ));
352 9 100       41 return unless String::Compare::ConstantTime::equals($msg_mac, $args{mac});
353              
354 4         14 my $cbc = Crypt::CBC->new(
355             -key => &$get_secret(
356             keynum => $keynum,
357             keyname => $self->keyname
358             ),
359             -cipher => 'Rijndael',
360             -salt => 1,
361             );
362 4         378 my $decrypted = $cbc->decrypt(decode_base64($cyphertext));
363 4 50       681 warn "Unable to decrypt $args{data} with keynum $keynum and keyname " . $self->keyname unless defined $decrypted;
364 4         38 my $data = decode_json($decrypted);
365 4         33 return $data;
366             }
367              
368             =head2 $self->encrypt_payload(data => $data)
369              
370             Encrypts data using I and returns result as a string including
371             both cyphertext and hmac in base-64 format. This can work on arbitrary data
372             structures, scalars, and references provided that the data can be serialized
373             as an attribute on a JSON document.
374              
375             =cut
376              
377             sub _to_payload {
378 2     2   3 my ($data) = @_;
379             return {
380 2         8 crypt_json_payload => $data,
381             crypt_json_version => $VERSION,
382             };
383             }
384              
385             sub _from_payload {
386 4     4   5 my ($payload) = @_;
387 4 100       11 return unless defined $payload;
388 2 50       13 return $payload->{crypt_json_payload} if exists $payload->{crypt_json_payload};
389 0         0 return $payload;
390             }
391              
392             sub encrypt_payload {
393 2     2 1 2954 my ($self, %args) = @_;
394 2         8 $args{data} = _to_payload($args{data});
395 2         9 my $enc = $self->encrypt_data(%args);
396 2         27 my $res = $enc->{data};
397 2         6 $res .= '.' . $enc->{mac};
398 2         8 return $res;
399             }
400              
401             =head2 $self->decrypt_payload(value => $value)
402              
403             Accepts payload encrypted with I, checks HMAC and decrypts
404             the value. Returns decripted value or undef if check or decryption has failed.
405              
406             =cut
407              
408             sub decrypt_payload {
409 5     5 1 3103 my ($self, %args) = @_;
410 5 50       14 return unless $args{value}; # nothing to decrypt
411 5 100       31 $args{value} =~ /^([A-Za-z0-9+\/*]+=*)\.([A-Za-z0-9+\/]+)$/ or return;
412 4         12 my ($data, $mac) = ($1, $2);
413 4         10 return _from_payload(
414             $self->decrypt_data(
415             data => $data,
416             mac => $mac,
417             ));
418             }
419              
420             1;