File Coverage

blib/lib/Net/SSH/Perl/Cipher/ChachaPoly.pm
Criterion Covered Total %
statement 64 74 86.4
branch 4 10 40.0
condition 1 5 20.0
subroutine 15 18 83.3
pod 3 9 33.3
total 87 116 75.0


line stmt bran cond sub pod time code
1             package Net::SSH::Perl::Cipher::ChachaPoly;
2              
3 1     1   7 use strict;
  1         2  
  1         29  
4              
5 1     1   6 use Net::SSH::Perl::Cipher;
  1         1  
  1         22  
6 1     1   5 use base qw( Net::SSH::Perl::Cipher );
  1         2  
  1         91  
7 1     1   6 use Carp qw( croak );
  1         1  
  1         68  
8              
9 1     1   7 use constant POLY1305_KEYLEN => 32;
  1         2  
  1         115  
10 1     1   11 use constant ONE => chr(1) . "\0" x 7; # NB little endian
  1         8  
  1         57  
11 1     1   7 use constant AADLEN => 4;
  1         2  
  1         91  
12              
13             unless (grep /^Net::SSH::Perl$/, @DynaLoader::dl_modules) {
14 1     1   7 use XSLoader;
  1         2  
  1         790  
15             XSLoader::load('Net::SSH::Perl');
16             }
17              
18             sub new {
19 6     6 1 16 my $class = shift;
20 6         25 my $ciph = bless { }, $class;
21 6 50       36 $ciph->init(@_) if @_;
22 6         18 $ciph;
23             }
24              
25 6     6 0 19 sub keysize { 64 }
26 0     0 0 0 sub blocksize { 8 }
27 6     6 0 15 sub authlen { 16 }
28 0     0 0 0 sub ivlen { 0 }
29              
30             sub init {
31 6     6 0 16 my $ciph = shift;
32 6         8 my $key = shift;
33              
34 6         14 my $size = $ciph->keysize/2;
35 6         58 my $mainkey = substr($key,0,$size);
36 6         19 my $headerkey = substr($key,$size,$size);
37 6         63 $ciph->{main} = Crypt::OpenSSH::ChachaPoly->new($mainkey);
38 6         40 $ciph->{header} = Crypt::OpenSSH::ChachaPoly->new($headerkey);
39             }
40              
41             sub _seqnr_bytes {
42 6     6   20 my ($ciph, $seqnr) = @_;
43 6         35 pack('N',0) . pack('N',$seqnr); # seqnr is only 32-bit
44             }
45              
46             sub encrypt {
47 3     3 1 3907 my($ciph, $data, $seqnr, $aadlen) = @_;
48 3   50     25 $aadlen ||= 0;
49              
50             # run chacha20 once to generate poly1305 key
51             # the iv is the packet sequence number
52 3         49 $seqnr = $ciph->_seqnr_bytes($seqnr);
53 3         45 $ciph->{main}->ivsetup($seqnr,'');
54 3         10 my $poly_key = "\0" x POLY1305_KEYLEN;
55 3         27 $poly_key = $ciph->{main}->encrypt($poly_key);
56              
57 3         7 my $aadenc;
58 3 50       20 if ($aadlen) {
59             # encrypt packet length from first four bytes of data
60 0         0 $ciph->{header}->ivsetup($seqnr,'');
61 0         0 $aadenc = $ciph->{header}->encrypt(substr($data,0,$aadlen));
62             }
63              
64             # set chacha's block counter to 1
65 3         11 $ciph->{main}->ivsetup($seqnr,ONE);
66 3         30 my $enc = $aadenc . $ciph->{main}->encrypt(substr($data,$aadlen));
67 3         28 $enc .= $ciph->{main}->poly1305($enc,$poly_key);
68 3         12 return $enc;
69             }
70              
71             sub decrypt {
72 3     3 1 19 my($ciph, $data, $seqnr, $aadlen) = @_;
73              
74             # run chacha20 once to generate poly1305 key
75             # the iv is the packet sequence number
76 3         6 $seqnr = $ciph->_seqnr_bytes($seqnr);
77 3         10 $ciph->{main}->ivsetup($seqnr,'');
78 3         9 my $poly_key = "\0" x POLY1305_KEYLEN;
79 3         15 $poly_key = $ciph->{main}->encrypt($poly_key);
80              
81 3         13 my $datalen = length($data) - $ciph->authlen;
82 3 50       15 if ($aadlen) {
83             $datalen = unpack('N', $ciph->{length} ||
84 0   0     0 eval {
85             $ciph->{header}->ivsetup($seqnr,'');
86             $ciph->{header}->decrypt(substr($data,0,$aadlen))
87             });
88             }
89 3         7 delete $ciph->{length};
90              
91             # check tag before decrypting packet
92 3         24 my $expected_tag = $ciph->{main}->poly1305(substr($data,0,$aadlen+$datalen),
93             $poly_key);
94 3         12 my $tag = substr($data,$aadlen+$datalen,$ciph->authlen);
95 3 50       17 croak "Invalid poly1305 tag"
96             if $expected_tag ne $tag;
97              
98             # set chacha's block counter to 1
99 3         13 $ciph->{main}->ivsetup($seqnr,ONE);
100             # return payload only
101 3         31 $ciph->{main}->decrypt(substr($data,$aadlen,$datalen));
102             }
103              
104             sub get_length {
105 0     0 0   my($ciph, $data, $seqnr) = @_;
106              
107 0 0         return if length($data) < AADLEN;
108 0           $seqnr = $ciph->_seqnr_bytes($seqnr);
109 0           $ciph->{header}->ivsetup($seqnr,'');
110             # save it so we do not have to decrypt again later in decrypt()
111 0           $ciph->{length} = $ciph->{header}->decrypt(substr($data,0,AADLEN));
112             }
113              
114             1;
115             __END__