File Coverage

lib/Noise/CipherState.pm
Criterion Covered Total %
statement 14 14 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 19 19 100.0


line stmt bran cond sub pod time code
1 4     4   47 use v5.42.0;
  4         12  
2 4     4   21 use feature 'class';
  4         6  
  4         554  
3 4     4   24 no warnings 'experimental::class';
  4         6  
  4         386  
4             #
5             class Noise::CipherState v0.0.1 {
6 4     4   2298 use Crypt::AuthEnc::ChaCha20Poly1305;
  4         14303  
  4         230  
7 4     4   2186 use Crypt::AuthEnc::GCM;
  4         1602  
  4         5979  
8             #
9             field $k : reader;
10             field $n : reader : writer(set_nonce) = 0;
11             field $cipher : param //= 'ChaChaPoly'; # 'ChaChaPoly' or 'AESGCM'
12              
13             method set_key ($key) {
14             $k = $key;
15             $n = 0;
16             }
17             method has_key () { return defined $k; }
18              
19             method encrypt_with_ad ( $ad, $plaintext ) {
20             return $plaintext unless defined $k;
21             my $nonce;
22             if ( $cipher eq 'ChaChaPoly' ) {
23              
24             # Noise: 32bit zeros + 64bit Little Endian counter
25             $nonce = pack( 'L<', 0 ) . pack( 'Q<', $n++ );
26             say "[DEBUG] Cipher encrypt: cipher=$cipher, k=" .
27             unpack( 'H*', $k ) .
28             ", nonce=" .
29             unpack( 'H*', $nonce ) . ", ad=" .
30             unpack( 'H*', $ad )
31             if $ENV{NOISE_DEBUG};
32             my $ae = Crypt::AuthEnc::ChaCha20Poly1305->new( $k, $nonce );
33             $ae->adata_add($ad);
34             return $ae->encrypt_add($plaintext) . $ae->encrypt_done();
35             }
36             elsif ( $cipher eq 'AESGCM' ) {
37              
38             # Noise: 32bit zeros + 64bit Big Endian counter
39             $nonce = pack( 'L>', 0 ) . pack( 'Q>', $n++ );
40             say "[DEBUG] Cipher encrypt: cipher=$cipher, k=" .
41             unpack( 'H*', $k ) .
42             ", nonce=" .
43             unpack( 'H*', $nonce ) . ', ad=' .
44             unpack( 'H*', $ad )
45             if $ENV{NOISE_DEBUG};
46             my $ae = Crypt::AuthEnc::GCM->new( 'AES', $k, $nonce );
47             $ae->adata_add($ad);
48             return $ae->encrypt_add($plaintext) . $ae->encrypt_done();
49             }
50             else {
51             die 'Unknown cipher: ' . $cipher;
52             }
53             }
54              
55             method decrypt_with_ad ( $ad, $ciphertext ) {
56             return $ciphertext unless defined $k;
57             my $nonce;
58             if ( $cipher eq 'ChaChaPoly' ) {
59             my $tag = substr( $ciphertext, -16 );
60             my $ct = substr( $ciphertext, 0, -16 );
61             $nonce = pack( 'L<', 0 ) . pack( 'Q<', $n++ );
62             say "[DEBUG] Cipher decrypt: cipher=$cipher, k=" .
63             unpack( 'H*', $k ) .
64             ", nonce=" .
65             unpack( 'H*', $nonce ) . ', ad=' .
66             unpack( 'H*', $ad )
67             if $ENV{NOISE_DEBUG};
68             my $ae = Crypt::AuthEnc::ChaCha20Poly1305->new( $k, $nonce );
69             $ae->adata_add($ad);
70             my $plaintext = $ae->decrypt_add($ct);
71             return $plaintext if $ae->decrypt_done($tag);
72             die 'CipherState: Decryption failed';
73             }
74             elsif ( $cipher eq 'AESGCM' ) {
75             my $tag = substr( $ciphertext, -16 );
76             my $ct = substr( $ciphertext, 0, -16 );
77             $nonce = pack( 'L>', 0 ) . pack( 'Q>', $n++ );
78             say "[DEBUG] Cipher decrypt: cipher=$cipher, k=" .
79             unpack( 'H*', $k ) .
80             ", nonce=" .
81             unpack( 'H*', $nonce ) . ', ad=' .
82             unpack( 'H*', $ad )
83             if $ENV{NOISE_DEBUG};
84             my $ae = Crypt::AuthEnc::GCM->new( 'AES', $k, $nonce );
85             $ae->adata_add($ad);
86             my $plaintext = $ae->decrypt_add($ct);
87             return $plaintext if $ae->decrypt_done($tag);
88             die 'CipherState: Decryption failed';
89             }
90             else {
91             die 'Unknown cipher: ' . $cipher;
92             }
93             }
94              
95             method rekey () {
96             my $nonce;
97             if ( $cipher eq 'ChaChaPoly' ) {
98             $nonce = pack( 'L<', 0 ) . pack( 'Q<', ~0 );
99             my $ae = Crypt::AuthEnc::ChaCha20Poly1305->new( $k, $nonce );
100             $ae->adata_add('');
101             my $new_k = $ae->encrypt_add( "\0" x 32 ) . $ae->encrypt_done();
102             $k = substr( $new_k, 0, 32 );
103             }
104             elsif ( $cipher eq 'AESGCM' ) {
105             $nonce = pack( 'L>', 0 ) . pack( 'Q>', ~0 );
106             my $ae = Crypt::AuthEnc::GCM->new( 'AES', $k, $nonce );
107             $ae->adata_add('');
108             my $new_k = $ae->encrypt_add( "\0" x 32 ) . $ae->encrypt_done();
109             $k = substr( $new_k, 0, 32 );
110             }
111             }
112             };
113             #
114             1;