File Coverage

blib/lib/Protocol/TLS/Context.pm
Criterion Covered Total %
statement 184 246 74.8
branch 81 130 62.3
condition 13 27 48.1
subroutine 30 33 90.9
pod 0 23 0.0
total 308 459 67.1


line stmt bran cond sub pod time code
1             package Protocol::TLS::Context;
2 2     2   54 use 5.008001;
  2         6  
3 2     2   10 use strict;
  2         3  
  2         40  
4 2     2   9 use warnings;
  2         4  
  2         52  
5 2     2   9 use Carp;
  2         4  
  2         120  
6 2     2   11 use Protocol::TLS::Trace qw(tracer bin2hex);
  2         3  
  2         97  
7 2     2   1223 use Protocol::TLS::RecordLayer;
  2         5  
  2         61  
8 2     2   1084 use Protocol::TLS::Extension;
  2         5  
  2         58  
9 2     2   1031 use Protocol::TLS::Crypto;
  2         6  
  2         72  
10             use Protocol::TLS::Constants
11 2         7652 qw(:end_types :state_types :alert_types :alert_desc :versions
12 2     2   11 :c_types :hs_types is_tls_version cipher_type const_name);
  2         4  
13              
14             # Mixin
15             our @ISA = qw(Protocol::TLS::RecordLayer Protocol::TLS::Extension);
16              
17             my %sp = (
18             connectionEnd => undef, # CLIENT, SERVER
19             PRFAlgorithm => undef, # tls_prf_sha256
20             BulkCipherAlgorithm => undef, # null, rc4, 3des, aes
21             CipherType => undef, # stream, block, aead
22             enc_key_length => undef,
23             block_length => undef,
24             fixed_iv_length => undef,
25             record_iv_length => undef,
26             MACAlgorithm => undef, # sha1, sha256
27             mac_length => undef,
28             mac_key_length => undef,
29             CompressionMethod => undef, # null
30             master_secret => ' ' x 48,
31             client_random => ' ' x 32,
32             server_random => ' ' x 32,
33             );
34              
35             my %kb = (
36             client_write_MAC_key => undef,
37             server_write_MAC_key => undef,
38             client_write_encryption_key => undef,
39             server_write_encryption_key => undef,
40             client_write_IV => undef,
41             server_write_IV => undef,
42             );
43              
44             sub copy_pending {
45 3     3 0 5 my $ctx = shift;
46 3         6 my $p = $ctx->{pending};
47             my $copy = {
48             cipher => $p->{cipher},
49 3         56 securityParameters => { %{ $p->{securityParameters} } },
50             tls_version => $p->{tls_version},
51             session_id => $p->{session_id},
52             compression => $p->{compression},
53 3         7 };
54 3         13 delete $copy->{securityParameters}->{client_random};
55 3         7 delete $copy->{securityParameters}->{server_random};
56 3         31 $copy;
57             }
58              
59             sub clear_pending {
60 7     7 0 17 my $ctx = shift;
61             $ctx->{pending} = {
62 7         212 securityParameters => {%sp},
63             key_block => {%kb},
64             tls_version => undef,
65             session_id => undef,
66             cipher => undef,
67             hs_messages => [],
68             compression => undef,
69             };
70 7         216 $ctx->{pending}->{securityParameters}->{connectionEnd} = $ctx->{type};
71             }
72              
73             sub new {
74 4     4 0 25 my ( $class, %args ) = @_;
75             croak "Connection end type must be specified: CLIENT or SERVER"
76             unless exists $args{type}
77             && ( $args{type} == CLIENT
78 4 50 66     50 || $args{type} == SERVER );
      33        
79              
80             my $self = bless {
81             type => $args{type},
82 4         62 crypto => Protocol::TLS::Crypto->new,
83             proposed => {},
84             pending => {},
85             current_decode => {},
86             current_encode => {},
87             session_id => undef,
88             tls_version => undef,
89             seq_read => 0, # 2^64-1
90             seq_write => 0, # 2^64-1
91             queue => [],
92             state => STATE_IDLE,
93             fragment => '',
94             }, $class;
95 4         26 $self->clear_pending;
96 4         41 $self->load_extensions('ServerName');
97              
98 4         14 $self->{pending}->{securityParameters}->{connectionEnd} = $args{type};
99             $self->{pending}->{securityParameters}
100 4 100       35 ->{ $args{type} == SERVER ? 'server_random' : 'client_random' } =
101             pack( 'N', time ) . $self->crypto->random(28);
102 4         502 $self;
103             }
104              
105             # Crypto backend object
106             sub crypto {
107 65     65 0 271 shift->{crypto};
108             }
109              
110             sub error {
111 0     0 0 0 my $self = shift;
112 0         0 tracer->debug("called error: @_\n");
113 0 0 0     0 if ( @_ && !$self->{shutdown} ) {
114 0         0 $self->{error} = shift;
115 0 0       0 $self->{on_error}->( $self->{error} ) if exists $self->{on_error};
116 0         0 $self->finish;
117             }
118 0         0 $self->{error};
119             }
120              
121             sub finish {
122 0     0 0 0 my $self = shift;
123 0 0       0 $self->enqueue( [ CTYPE_ALERT, FATAL, $self->{error} ] )
124             unless $self->shutdown;
125 0         0 $self->shutdown(1);
126             }
127              
128             sub close {
129 5     5 0 3027 my $self = shift;
130 5 100       20 $self->enqueue( [ CTYPE_ALERT, FATAL, CLOSE_NOTIFY ] )
131             unless $self->shutdown;
132 5         25 $self->shutdown(1);
133             }
134              
135             sub shutdown {
136 24     24 0 48 my $self = shift;
137 24 100       98 $self->{shutdown} = shift if @_;
138 24         97 $self->{shutdown};
139             }
140              
141             sub enqueue {
142 15     15 0 44 my ( $self, @records ) = @_;
143 15         45 for (@records) {
144 20 100       101 tracer->debug(
145             "enqueue "
146             . const_name( 'c_types', $_->[0] )
147             . (
148             $_->[0] == CTYPE_HANDSHAKE
149             ? "/" . const_name( 'hs_types', $_->[1] )
150             : ''
151             )
152             . "\n"
153             );
154 20         39 push @{ $self->{queue} }, $self->record_encode( TLS_v12, @$_ );
  20         142  
155 20 100       145 $self->state_machine( 'send', $_->[0],
156             $_->[0] == CTYPE_HANDSHAKE ? $_->[1] : () );
157             }
158             }
159              
160             sub dequeue {
161 34     34 0 52 my $self = shift;
162 34         58 shift @{ $self->{queue} };
  34         131  
163             }
164              
165             sub application_data {
166 3     3 0 9 my ( $ctx, $buf_ref, $buf_offset, $length ) = @_;
167 3 50 33     23 if ( exists $ctx->{on_data} && $ctx->state == STATE_OPEN ) {
168 3         24 $ctx->{on_data}->( $ctx, substr $$buf_ref, $buf_offset, $length );
169             }
170 3         1380 $length;
171             }
172              
173             sub send {
174 3     3 0 1991 my ( $ctx, $data ) = @_;
175 3 50       21 if ( $ctx->state == STATE_OPEN ) {
176 3         23 $ctx->enqueue( [ CTYPE_APPLICATION_DATA, $data ] );
177             }
178             }
179              
180             sub state_machine {
181 37     37 0 229 my ( $ctx, $action, $c_type, $hs_type ) = @_;
182 37         154 my $prev_state = $ctx->state;
183              
184 37 100       276 if ( $c_type == CTYPE_ALERT ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    0          
185              
186             }
187             elsif ( $c_type == CTYPE_APPLICATION_DATA ) {
188 6 50       68 if ( $prev_state != STATE_OPEN ) {
189 0         0 tracer->error("Handshake was not complete\n");
190 0         0 $ctx->error(UNEXPECTED_MESSAGE);
191             }
192             }
193              
194             # IDLE state (waiting for ClientHello)
195             elsif ( $prev_state == STATE_IDLE ) {
196 4 50 33     17 if ( $c_type != CTYPE_HANDSHAKE && $hs_type != HSTYPE_CLIENT_HELLO ) {
197 0         0 tracer->error("Only ClientHello allowed in IDLE state\n");
198 0         0 $ctx->error(UNEXPECTED_MESSAGE);
199             }
200             else {
201 4         12 $ctx->state(STATE_HS_START);
202             }
203             }
204              
205             # Start Handshake (waiting for ServerHello)
206             elsif ( $prev_state == STATE_HS_START ) {
207 4 50 33     60 if ( $c_type != CTYPE_HANDSHAKE && $hs_type != HSTYPE_SERVER_HELLO ) {
    100 100        
208 0         0 tracer->error(
209             "Only ServerHello allowed at Handshake Start state\n");
210 0         0 $ctx->error(UNEXPECTED_MESSAGE);
211             }
212             elsif ( defined $ctx->{proposed}->{session_id}
213             && $ctx->{proposed}->{session_id} eq $ctx->{pending}->{session_id} )
214             {
215 1         10 $ctx->state(STATE_SESS_RESUME);
216             }
217             else {
218 3         10 $ctx->state(STATE_SESS_NEW);
219             }
220             }
221              
222             # STATE_SESS_RESUME
223             elsif ( $prev_state == STATE_SESS_RESUME ) {
224 2 100       14 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
225 1 50       5 if ( $hs_type == HSTYPE_FINISHED ) {
226 1         4 $ctx->state(STATE_HS_RESUME);
227             }
228             }
229             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
230 1         6 $ctx->change_cipher_spec($action);
231             }
232             else {
233 0         0 tracer->error("Unexpected Handshake type\n");
234 0         0 $ctx->error(UNEXPECTED_MESSAGE);
235             }
236             }
237              
238             # STATE_HS_RESUME
239             elsif ( $prev_state == STATE_HS_RESUME ) {
240 2 100 66     21 if ( $c_type == CTYPE_HANDSHAKE && $hs_type == HSTYPE_FINISHED ) {
    50          
241 1         4 $ctx->state(STATE_OPEN);
242             }
243             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
244 1         3 $ctx->change_cipher_spec($action);
245             }
246             else {
247 0         0 tracer->error("Unexpected Handshake type\n");
248 0         0 $ctx->error(UNEXPECTED_MESSAGE);
249             }
250             }
251              
252             # STATE_SESS_NEW
253             elsif ( $prev_state == STATE_SESS_NEW ) {
254 6 50       14 if ( $c_type == CTYPE_HANDSHAKE ) {
255 6 100       22 if ( $hs_type == HSTYPE_SERVER_HELLO_DONE ) {
256 3         48 $ctx->state(STATE_HS_HALF);
257             }
258             }
259             else {
260 0         0 tracer->error("Unexpected Handshake type\n");
261 0         0 $ctx->error(UNEXPECTED_MESSAGE);
262             }
263             }
264              
265             # STATE_HS_HALF
266             elsif ( $prev_state == STATE_HS_HALF ) {
267 6 100       21 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
268 4 100       23 if ( $hs_type == HSTYPE_FINISHED ) {
269 2         6 $ctx->state(STATE_HS_FULL);
270             }
271             }
272             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
273 2         10 $ctx->change_cipher_spec($action);
274             }
275             else {
276 0         0 tracer->error("Unexpected Handshake type\n");
277 0         0 $ctx->error(UNEXPECTED_MESSAGE);
278             }
279             }
280              
281             # STATE_HS_FULL
282             elsif ( $prev_state == STATE_HS_FULL ) {
283 4 100       17 if ( $c_type == CTYPE_HANDSHAKE ) {
    50          
284 2 50       10 if ( $hs_type == HSTYPE_FINISHED ) {
285 2         7 $ctx->state(STATE_OPEN);
286             }
287             }
288             elsif ( $c_type == CTYPE_CHANGE_CIPHER_SPEC ) {
289 2         11 $ctx->change_cipher_spec($action);
290             }
291             else {
292 0         0 tracer->error("Unexpected Handshake type\n");
293 0         0 $ctx->error(UNEXPECTED_MESSAGE);
294             }
295             }
296              
297             # TODO: ReNegotiation
298             elsif ( $prev_state == STATE_OPEN ) {
299 0         0 tracer->warning("ReNegotiation is not yet supported\n");
300             }
301             }
302              
303             sub generate_key_block {
304 3     3 0 6 my $ctx = shift;
305 3         8 my $sp = $ctx->{pending}->{securityParameters};
306 3         7 my $kb = $ctx->{pending}->{key_block};
307             ( my $da, $sp->{BulkCipherAlgorithm}, $sp->{MACAlgorithm} ) =
308 3         17 cipher_type( $ctx->{pending}->{cipher} );
309              
310             tracer->debug( "Generating key block for cipher "
311 3         13 . const_name( 'ciphers', $ctx->{pending}->{cipher} ) );
312              
313             $sp->{mac_length} = $sp->{mac_key_length} =
314             $sp->{MACAlgorithm} eq 'SHA' ? 20
315             : $sp->{MACAlgorithm} eq 'SHA256' ? 32
316 3 0       59 : $sp->{MACAlgorithm} eq 'MD5' ? 16
    0          
    50          
317             : 0;
318              
319             (
320             $sp->{CipherType}, $sp->{enc_key_length},
321             $sp->{fixed_iv_length}, $sp->{block_length}
322             )
323             =
324             $sp->{BulkCipherAlgorithm} eq 'AES_128_CBC' ? ( 'block', 16, 16, 16 )
325             : $sp->{BulkCipherAlgorithm} eq 'AES_256_CBC' ? ( 'block', 32, 16, 16 )
326             : $sp->{BulkCipherAlgorithm} eq '3DES_EDE_CBC' ? ( 'block', 24, 8, 8 )
327 3 0       34 : $sp->{BulkCipherAlgorithm} eq 'RC4_128' ? ( 'stream', 16, 0, undef )
    0          
    0          
    50          
328             : ( 'stream', 0, 0, undef );
329              
330             (
331             $kb->{client_write_MAC_key},
332             $kb->{server_write_MAC_key},
333             $kb->{client_write_encryption_key},
334             $kb->{server_write_encryption_key},
335             $kb->{client_write_IV},
336             $kb->{server_write_IV}
337             )
338             = unpack sprintf(
339             'a%i' x 6,
340             ( $sp->{mac_key_length} ) x 2,
341             ( $sp->{enc_key_length} ) x 2,
342             ( $sp->{fixed_iv_length} ) x 2,
343             ),
344             $ctx->crypto->PRF(
345             $sp->{master_secret},
346             "key expansion",
347             $sp->{server_random} . $sp->{client_random},
348             $sp->{mac_key_length} * 2 +
349             $sp->{enc_key_length} * 2 +
350 3         47 $sp->{fixed_iv_length} * 2
351             );
352              
353 3         11 ();
354             }
355              
356             sub change_cipher_spec {
357 6     6 0 49 my ( $ctx, $action ) = @_;
358 6         24 tracer->debug("Apply cipher spec $action...\n");
359              
360 6         20 my $sp = $ctx->{pending}->{securityParameters};
361 6         14 my $kb = $ctx->{pending}->{key_block};
362 6 100       34 $ctx->generate_key_block unless defined $kb->{client_write_MAC_key};
363             my $cur =
364 6 100       24 $action eq 'recv' ? $ctx->{current_decode} : $ctx->{current_encode};
365 6         195 $cur->{securityParameters}->{$_} = $sp->{$_} for keys %$sp;
366 6         98 $cur->{key_block}->{$_} = $kb->{$_} for keys %$kb;
367             }
368              
369             sub state {
370 60     60 0 92 my $ctx = shift;
371 60 100       155 if (@_) {
372 17         34 my $state = shift;
373             $ctx->{on_change_state}->( $ctx, $ctx->{state}, $state )
374 17 100       89 if exists $ctx->{on_change_state};
375              
376 17         37 $ctx->{state} = $state;
377              
378             # Exec callbacks for new state
379 17 100 66     106 if ( exists $ctx->{cb} && exists $ctx->{cb}->{$state} ) {
380 8         15 for my $cb ( @{ $ctx->{cb}->{$state} } ) {
  8         27  
381 8         31 $cb->($ctx);
382             }
383             }
384             }
385 60         228 $ctx->{state};
386             }
387              
388             sub state_cb {
389 15     15 0 30 my ( $ctx, $state, $cb ) = @_;
390 15         20 push @{ $ctx->{cb}->{$state} }, $cb;
  15         71  
391             }
392              
393             sub validate_server_hello {
394 3     3 0 23 my ( $ctx, %h ) = @_;
395 3         10 my $tls_v = is_tls_version( $h{version} );
396 3 50       11 if ( !defined $tls_v ) {
397 0         0 tracer->error("server TLS version $h{version} not recognized\n");
398 0         0 $ctx->error(HANDSHAKE_FAILURE);
399 0         0 return undef;
400             }
401 3         8 my $p = $ctx->{pending};
402 3         11 my $pro = $ctx->{proposed};
403              
404 3 50       13 if ( $tls_v < $pro->{tls_version} ) {
405 0         0 tracer->error("server TLS version $tls_v is not supported\n");
406 0         0 $ctx->error(PROTOCOL_VERSION);
407 0         0 return undef;
408             }
409              
410 3 50       6 if ( !grep { $h{compression} == $_ } @{ $pro->{compression} } ) {
  3         18  
  3         8  
411 0         0 tracer->error("server compression not supported\n");
412 0         0 $ctx->error(HANDSHAKE_FAILURE);
413 0         0 return undef;
414             }
415              
416 3 50       6 if ( !grep { $h{cipher} == $_ } @{ $pro->{ciphers} } ) {
  7         27  
  3         9  
417 0         0 tracer->error("server cipher not accepted\n");
418 0         0 $ctx->error(HANDSHAKE_FAILURE);
419 0         0 return undef;
420             }
421              
422 3         8 $p->{tls_version} = $tls_v;
423 3         11 $p->{securityParameters}->{server_random} = $h{random};
424 3         6 $p->{session_id} = $h{session_id};
425             $p->{securityParameters}->{CompressionMethod} = $p->{compression} =
426 3         8 $h{compression};
427 3         6 $p->{cipher} = $h{cipher};
428 3         14 1;
429             }
430              
431             sub validate_client_hello {
432 1     1 0 12 my ( $ctx, %h ) = @_;
433 1         8 my $tls_v = is_tls_version( $h{tls_version} );
434 1 50       4 if ( !defined $tls_v ) {
435 0         0 tracer->error(
436             "client's TLS version $h{tls_version} is not recognized\n");
437 0         0 $ctx->error(HANDSHAKE_FAILURE);
438 0         0 return undef;
439             }
440 1         4 my $p = $ctx->{pending};
441 1         2 my $pro = $ctx->{proposed};
442              
443 1 50       4 if ( $tls_v < $pro->{tls_version} ) {
444 0         0 tracer->error("client's TLS version $tls_v is not supported\n");
445 0         0 $ctx->error(PROTOCOL_VERSION);
446 0         0 return undef;
447             }
448              
449 1         2 for my $c ( @{ $pro->{compression} } ) {
  1         4  
450 1 50       2 next unless grep { $c == $_ } @{ $h{compression} };
  1         6  
  1         3  
451 1         2 $p->{securityParameters}->{CompressionMethod} = $c;
452 1         3 last;
453             }
454              
455 1 50       9 if ( !exists $p->{securityParameters}->{CompressionMethod} ) {
456 0         0 tracer->error("client's compression not supported\n");
457 0         0 $ctx->error(HANDSHAKE_FAILURE);
458 0         0 return undef;
459             }
460              
461 1         2 $p->{tls_version} = $tls_v;
462 1         3 $p->{securityParameters}->{client_random} = $h{random};
463 1         2 $p->{session_id} = $h{session_id};
464              
465             # Choose first defined cipher
466 1         2 for my $cipher ( @{ $pro->{ciphers} } ) {
  1         3  
467 1 50       2 next unless grep { $cipher == $_ } @{ $h{ciphers} };
  6         14  
  1         2  
468 1         2 $p->{cipher} = $cipher;
469 1         2 last;
470             }
471              
472 1 50       4 if ( !exists $p->{cipher} ) {
473 0         0 tracer->error("client's ciphers not supported\n");
474 0         0 $ctx->error(HANDSHAKE_FAILURE);
475 0         0 return undef;
476             }
477              
478 1         4 1;
479             }
480              
481             sub validate_client_key {
482 0     0 0 0 my ( $ctx, $pkey ) = @_;
483 0         0 my $p = $ctx->{pending};
484 0         0 my $sp = $p->{securityParameters};
485 0         0 my ( $da, $ca, $mac ) = cipher_type( $p->{cipher} );
486              
487 0 0       0 if ( $da eq 'RSA' ) {
488 0         0 my $preMasterSecret = $ctx->crypto->rsa_decrypt( $ctx->{key}, $pkey );
489              
490             $sp->{master_secret} = $ctx->crypto->PRF(
491             $preMasterSecret,
492             "master secret",
493 0         0 $sp->{client_random} . $sp->{server_random}, 48
494             );
495              
496             }
497             else {
498 0         0 die "not implemented";
499             }
500              
501             }
502              
503             sub peer_finished {
504 3     3 0 7 my $ctx = shift;
505 3 50       20 $ctx->_finished( $ctx->{type} == CLIENT ? SERVER : CLIENT );
506             }
507              
508             sub finished {
509 3     3 0 5 my $ctx = shift;
510 3 50       18 $ctx->_finished( $ctx->{type} == CLIENT ? CLIENT : SERVER );
511             }
512              
513             sub _finished {
514 6     6   18 my ( $ctx, $type ) = @_;
515             $ctx->crypto->PRF(
516             $ctx->{pending}->{securityParameters}->{master_secret},
517             ( $type == CLIENT ? 'client' : 'server' ) . ' finished',
518 6 100       19 $ctx->crypto->PRF_hash( join '', @{ $ctx->{pending}->{hs_messages} } ),
  6         60  
519             12
520             );
521             }
522              
523             sub validate_finished {
524 3     3 0 6 my ( $ctx, $message ) = @_;
525              
526 3         10 my $p = $ctx->{pending};
527 3         9 my $sp = $p->{securityParameters};
528 3         9 my $crypto = $ctx->crypto;
529              
530 3         13 my $finished = $ctx->peer_finished;
531 3         17 tracer->debug( "finished expected: " . bin2hex($finished) );
532 3         18 tracer->debug( "finished received: " . bin2hex($message) );
533              
534 3 50       18 if ( $finished ne $message ) {
535 0         0 tracer->error("finished not match");
536 0         0 $ctx->error(HANDSHAKE_FAILURE);
537 0         0 return;
538             }
539 3         18 1;
540             }
541              
542             1