| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
3
|
|
|
3
|
|
65
|
use v5.42.0; |
|
|
3
|
|
|
|
|
14
|
|
|
2
|
3
|
|
|
3
|
|
16
|
use feature 'class'; |
|
|
3
|
|
|
|
|
3
|
|
|
|
3
|
|
|
|
|
367
|
|
|
3
|
3
|
|
|
3
|
|
16
|
no warnings 'experimental::class'; |
|
|
3
|
|
|
|
|
4
|
|
|
|
3
|
|
|
|
|
248
|
|
|
4
|
|
|
|
|
|
|
# |
|
5
|
|
|
|
|
|
|
class Noise::HandshakeState v0.0.2 { |
|
6
|
3
|
|
|
3
|
|
1597
|
use Noise::SymmetricState; |
|
|
3
|
|
|
|
|
9
|
|
|
|
3
|
|
|
|
|
121
|
|
|
7
|
3
|
|
|
3
|
|
1607
|
use Noise::Pattern; |
|
|
3
|
|
|
|
|
9
|
|
|
|
3
|
|
|
|
|
120
|
|
|
8
|
3
|
|
|
3
|
|
1714
|
use Crypt::PK::X25519; |
|
|
3
|
|
|
|
|
33798
|
|
|
|
3
|
|
|
|
|
161
|
|
|
9
|
3
|
|
|
3
|
|
2061
|
use Crypt::PK::ECC; |
|
|
3
|
|
|
|
|
10383
|
|
|
|
3
|
|
|
|
|
8176
|
|
|
10
|
|
|
|
|
|
|
# |
|
11
|
|
|
|
|
|
|
my %DH_CONFIG = ( |
|
12
|
|
|
|
|
|
|
25519 => { class => 'Crypt::PK::X25519', pub_len => 32 }, |
|
13
|
|
|
|
|
|
|
P256 => { class => 'Crypt::PK::ECC', pub_len => 65, params => 'secp256r1' }, |
|
14
|
|
|
|
|
|
|
P384 => { class => 'Crypt::PK::ECC', pub_len => 97, params => 'secp384r1' }, |
|
15
|
|
|
|
|
|
|
P521 => { class => 'Crypt::PK::ECC', pub_len => 133, params => 'secp521r1' } |
|
16
|
|
|
|
|
|
|
); |
|
17
|
|
|
|
|
|
|
# |
|
18
|
|
|
|
|
|
|
field $s : reader : param //= undef; # Local static key |
|
19
|
|
|
|
|
|
|
field $e : reader : param //= undef; # Local ephemeral key |
|
20
|
|
|
|
|
|
|
field $rs : reader : param //= undef; # Remote static public key |
|
21
|
|
|
|
|
|
|
field $re : reader : param //= undef; # Remote ephemeral public key |
|
22
|
|
|
|
|
|
|
field $psks : reader : param //= []; # Array of pre-shared keys |
|
23
|
|
|
|
|
|
|
field $psk_idx = 0; |
|
24
|
|
|
|
|
|
|
field $initiator : param; # Boolean |
|
25
|
|
|
|
|
|
|
field $pattern : param; # Pattern name or object |
|
26
|
|
|
|
|
|
|
field $prologue : param //= ''; # Optional prologue |
|
27
|
|
|
|
|
|
|
field $symmetric_state : reader; |
|
28
|
|
|
|
|
|
|
field $msg_idx = 0; |
|
29
|
|
|
|
|
|
|
field $dh_name; |
|
30
|
|
|
|
|
|
|
field $dh_len; |
|
31
|
|
|
|
|
|
|
# |
|
32
|
|
|
|
|
|
|
ADJUST { |
|
33
|
|
|
|
|
|
|
my $full_name = $pattern; |
|
34
|
|
|
|
|
|
|
if ( $full_name !~ /^Noise(?:PSK)?_/ ) { |
|
35
|
|
|
|
|
|
|
$full_name = 'Noise_' . $pattern . '_25519_ChaChaPoly_SHA256'; |
|
36
|
|
|
|
|
|
|
} |
|
37
|
|
|
|
|
|
|
my $is_psk_prefix = ( $full_name =~ /^NoisePSK_/ ); |
|
38
|
|
|
|
|
|
|
my ( $p_name, $dh, $cipher_name, $hash_name ) = $full_name =~ /^Noise(?:PSK)?_([^_]+)_([^_]+)_([^_]+)_([^_]+)$/; |
|
39
|
|
|
|
|
|
|
die 'Invalid protocol name: ' . $full_name unless $p_name; |
|
40
|
|
|
|
|
|
|
if ( $is_psk_prefix && $p_name !~ /psk/ ) { |
|
41
|
|
|
|
|
|
|
$p_name .= 'psk0'; |
|
42
|
|
|
|
|
|
|
} |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# The protocol name used for h_init must be the standard Noise_... format |
|
45
|
|
|
|
|
|
|
my $noise_name = 'Noise_' . $p_name . '_' . $dh . '_' . $cipher_name . '_' . $hash_name; |
|
46
|
|
|
|
|
|
|
$dh_name = $dh; |
|
47
|
|
|
|
|
|
|
$dh_len = $DH_CONFIG{$dh_name}->{pub_len} or die "Unsupported DH: $dh_name"; |
|
48
|
|
|
|
|
|
|
if ( ref $pattern ne 'Noise::Pattern' ) { |
|
49
|
|
|
|
|
|
|
$pattern = Noise::Pattern->new( name => $p_name ); |
|
50
|
|
|
|
|
|
|
} |
|
51
|
|
|
|
|
|
|
$symmetric_state = Noise::SymmetricState->new( cipher => $cipher_name, hash => $hash_name ); |
|
52
|
|
|
|
|
|
|
$symmetric_state->initialize_symmetric($full_name); # using full_name matched noise-c Vector 16 better |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Mix prologue (always mixed, even if empty) |
|
55
|
|
|
|
|
|
|
$symmetric_state->mix_hash($prologue); |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Process pre-messages |
|
58
|
|
|
|
|
|
|
my $pre = $pattern->pre_msg; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Process initiator pre-messages (index 0) |
|
61
|
|
|
|
|
|
|
for my $token ( $pre->[0]->@* ) { |
|
62
|
|
|
|
|
|
|
if ( $token eq 's' ) { |
|
63
|
|
|
|
|
|
|
my $pk = $initiator ? $s : $rs; |
|
64
|
|
|
|
|
|
|
$symmetric_state->mix_hash( $pk->export_key_raw('public') // '' ); |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
elsif ( $token eq 'e' ) { |
|
67
|
|
|
|
|
|
|
my $pk = $initiator ? $e : $re; |
|
68
|
|
|
|
|
|
|
$symmetric_state->mix_hash( $pk->export_key_raw('public') // '' ); |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
} |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Process responder pre-messages (index 1) |
|
73
|
|
|
|
|
|
|
for my $token ( $pre->[1]->@* ) { |
|
74
|
|
|
|
|
|
|
if ( $token eq 's' ) { |
|
75
|
|
|
|
|
|
|
my $pk = $initiator ? $rs : $s; |
|
76
|
|
|
|
|
|
|
$symmetric_state->mix_hash( $pk->export_key_raw('public') // '' ); |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
elsif ( $token eq 'e' ) { |
|
79
|
|
|
|
|
|
|
my $pk = $initiator ? $re : $e; |
|
80
|
|
|
|
|
|
|
$symmetric_state->mix_hash( $pk->export_key_raw('public') // '' ); |
|
81
|
|
|
|
|
|
|
} |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
method _new_dh_obj { $DH_CONFIG{$dh_name}->{class}->new() } |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
method _dh_generate_key($obj) { |
|
87
|
|
|
|
|
|
|
if ( $dh_name =~ /^P/ ) { |
|
88
|
|
|
|
|
|
|
$obj->generate_key( $DH_CONFIG{$dh_name}->{params} ); |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
else { |
|
91
|
|
|
|
|
|
|
$obj->generate_key(); |
|
92
|
|
|
|
|
|
|
} |
|
93
|
|
|
|
|
|
|
} |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
method _dh_import_key( $obj, $raw, $type ) { |
|
96
|
|
|
|
|
|
|
if ( $dh_name =~ /^P/ ) { |
|
97
|
|
|
|
|
|
|
$obj->import_key_raw( $raw, $DH_CONFIG{$dh_name}->{params} ); |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
else { |
|
100
|
|
|
|
|
|
|
$obj->import_key_raw( $raw, $type ); |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
method write_message ($payload) { |
|
105
|
|
|
|
|
|
|
my $tokens = $pattern->msg_seq->[ $msg_idx++ ] or die 'No more messages in pattern'; |
|
106
|
|
|
|
|
|
|
my $message = ''; |
|
107
|
|
|
|
|
|
|
for my $token (@$tokens) { |
|
108
|
|
|
|
|
|
|
if ( $token eq 'e' ) { |
|
109
|
|
|
|
|
|
|
$e //= $self->_new_dh_obj(); |
|
110
|
|
|
|
|
|
|
$self->_dh_generate_key($e) unless $e->is_private; |
|
111
|
|
|
|
|
|
|
my $pub = $e->export_key_raw('public'); |
|
112
|
|
|
|
|
|
|
$message .= $pub; |
|
113
|
|
|
|
|
|
|
$symmetric_state->mix_hash($pub); |
|
114
|
|
|
|
|
|
|
$symmetric_state->mix_key($pub) if $pattern->has_psk; |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
elsif ( $token eq 's' ) { |
|
117
|
|
|
|
|
|
|
my $pub = $s->export_key_raw('public'); |
|
118
|
|
|
|
|
|
|
$message .= $symmetric_state->encrypt_and_hash($pub); |
|
119
|
|
|
|
|
|
|
$symmetric_state->mix_key($pub) if $pattern->has_psk; |
|
120
|
|
|
|
|
|
|
} |
|
121
|
|
|
|
|
|
|
elsif ( $token eq 'ee' ) { # ee: DH(initiator_e, responder_e) |
|
122
|
|
|
|
|
|
|
die 'ee failed: e or re undefined' unless $e && $re; |
|
123
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($re) ); |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
elsif ( $token eq 'es' ) { # es: DH(initiator_e, responder_s) |
|
126
|
|
|
|
|
|
|
if ($initiator) { |
|
127
|
|
|
|
|
|
|
die 'es failed: e or rs undefined' unless $e && $rs; |
|
128
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($rs) ); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
else { |
|
131
|
|
|
|
|
|
|
die 'es failed: s or re undefined' unless $s && $re; |
|
132
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($re) ); |
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
elsif ( $token eq 'se' ) { # se: DH(initiator_s, responder_e) |
|
136
|
|
|
|
|
|
|
if ($initiator) { |
|
137
|
|
|
|
|
|
|
die 'se failed: s or re undefined' unless $s && $re; |
|
138
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($re) ); |
|
139
|
|
|
|
|
|
|
} |
|
140
|
|
|
|
|
|
|
else { |
|
141
|
|
|
|
|
|
|
die 'se failed: e or rs undefined' unless $e && $rs; |
|
142
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($rs) ); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
elsif ( $token eq 'ss' ) { # ss: DH(initiator_s, responder_s) |
|
146
|
|
|
|
|
|
|
die 'ss failed: s or rs undefined' unless $s && $rs; |
|
147
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($rs) ); |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
elsif ( $token eq 'psk' ) { |
|
150
|
|
|
|
|
|
|
$symmetric_state->mix_key_and_hash( $psks->[ $psk_idx++ ] // die 'Missing PSK at index ' . $psk_idx ); |
|
151
|
|
|
|
|
|
|
} |
|
152
|
|
|
|
|
|
|
} |
|
153
|
|
|
|
|
|
|
return $message . $symmetric_state->encrypt_and_hash($payload); |
|
154
|
|
|
|
|
|
|
} |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
method read_message ($message) { |
|
157
|
|
|
|
|
|
|
my $tokens = $pattern->msg_seq->[ $msg_idx++ ] or die 'No more messages in pattern'; |
|
158
|
|
|
|
|
|
|
my $pos = 0; |
|
159
|
|
|
|
|
|
|
for my $token (@$tokens) { |
|
160
|
|
|
|
|
|
|
if ( $token eq 'e' ) { |
|
161
|
|
|
|
|
|
|
my $pub_raw = substr( $message, $pos, $dh_len ); |
|
162
|
|
|
|
|
|
|
$pos += $dh_len; |
|
163
|
|
|
|
|
|
|
$re = $self->_new_dh_obj(); |
|
164
|
|
|
|
|
|
|
$self->_dh_import_key( $re, $pub_raw, 'public' ); |
|
165
|
|
|
|
|
|
|
$symmetric_state->mix_hash($pub_raw); |
|
166
|
|
|
|
|
|
|
if ( $pattern->has_psk ) { $symmetric_state->mix_key($pub_raw); } |
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
elsif ( $token eq 's' ) { |
|
169
|
|
|
|
|
|
|
my $len = $symmetric_state->cipher_state->has_key ? $dh_len + 16 : $dh_len; |
|
170
|
|
|
|
|
|
|
my $ct = substr( $message, $pos, $len ); |
|
171
|
|
|
|
|
|
|
$pos += $len; |
|
172
|
|
|
|
|
|
|
my $pub_raw = $symmetric_state->decrypt_and_hash($ct); |
|
173
|
|
|
|
|
|
|
$rs = $self->_new_dh_obj(); |
|
174
|
|
|
|
|
|
|
$self->_dh_import_key( $rs, $pub_raw, 'public' ); |
|
175
|
|
|
|
|
|
|
$symmetric_state->mix_key($pub_raw) if $pattern->has_psk; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
elsif ( $token eq 'ee' ) { # ee: DH(initiator_e, responder_e) |
|
178
|
|
|
|
|
|
|
die 'ee failed: e or re undefined' unless $e && $re; |
|
179
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($re) ); |
|
180
|
|
|
|
|
|
|
} |
|
181
|
|
|
|
|
|
|
elsif ( $token eq 'es' ) { # es: DH(initiator_e, responder_s) |
|
182
|
|
|
|
|
|
|
if ($initiator) { |
|
183
|
|
|
|
|
|
|
die 'es failed: e or rs undefined' unless $e && $rs; |
|
184
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($rs) ); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
else { |
|
187
|
|
|
|
|
|
|
die 'es failed: s or re undefined' unless $s && $re; |
|
188
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($re) ); |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
elsif ( $token eq 'se' ) { # se: DH(initiator_s, responder_e) |
|
192
|
|
|
|
|
|
|
if ($initiator) { |
|
193
|
|
|
|
|
|
|
die 'se failed: s or re undefined' unless $s && $re; |
|
194
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($re) ); |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
else { |
|
197
|
|
|
|
|
|
|
die 'se failed: e or rs undefined' unless $e && $rs; |
|
198
|
|
|
|
|
|
|
$symmetric_state->mix_key( $e->shared_secret($rs) ); |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
elsif ( $token eq 'ss' ) { # ss: DH(initiator_s, responder_s) |
|
202
|
|
|
|
|
|
|
die 'ss failed: s or rs undefined' unless $s && $rs; |
|
203
|
|
|
|
|
|
|
$symmetric_state->mix_key( $s->shared_secret($rs) ); |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
elsif ( $token eq 'psk' ) { |
|
206
|
|
|
|
|
|
|
$symmetric_state->mix_key_and_hash( $psks->[ $psk_idx++ ] // die 'Missing PSK at index ' . $psk_idx ); |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
} |
|
209
|
|
|
|
|
|
|
my $payload = $symmetric_state->decrypt_and_hash( substr( $message, $pos ) ); |
|
210
|
|
|
|
|
|
|
return $payload; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
method split () { $symmetric_state->split() } |
|
213
|
|
|
|
|
|
|
}; |
|
214
|
|
|
|
|
|
|
# |
|
215
|
|
|
|
|
|
|
1; |