File Coverage

blib/lib/Authen/SASL/Perl/DIGEST_MD5.pm
Criterion Covered Total %
statement 233 313 74.4
branch 78 148 52.7
condition 34 65 52.3
subroutine 27 32 84.3
pod 0 9 0.0
total 372 567 65.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2003-2009 Graham Barr, Djamel Boudjerda, Paul Connolly, Julian
2             # Onions, Nexor and Yann Kerherve.
3             # All rights reserved. This program is free software; you can redistribute
4             # it and/or modify it under the same terms as Perl itself.
5              
6             # See http://www.ietf.org/rfc/rfc2831.txt for details
7              
8             package Authen::SASL::Perl::DIGEST_MD5;
9             $Authen::SASL::Perl::DIGEST_MD5::VERSION = '2.1700'; # TRIAL
10 5     5   954 use strict;
  5         9  
  5         164  
11 5     5   27 use warnings;
  5         9  
  5         150  
12 5     5   26 use vars qw($VERSION @ISA $CNONCE $NONCE);
  5         10  
  5         356  
13 5     5   31 use Digest::MD5 qw(md5_hex md5);
  5         9  
  5         264  
14 5     5   474 use Digest::HMAC_MD5 qw(hmac_md5);
  5         1411  
  5         21624  
15              
16             # TODO: complete qop support in server, should be configurable
17              
18             $VERSION = "2.14";
19             @ISA = qw(Authen::SASL::Perl);
20              
21             my %secflags = (
22             noplaintext => 1,
23             noanonymous => 1,
24             );
25              
26             # some have to be quoted - some don't - sigh!
27             my (%cqdval, %sqdval);
28             @cqdval{qw(
29             username authzid realm nonce cnonce digest-uri
30             )} = ();
31              
32             ## ...and server behaves different than client - double sigh!
33             @sqdval{keys %cqdval, qw(qop cipher)} = ();
34             # username authzid realm nonce cnonce digest-uri qop cipher
35             #)} = ();
36              
37             my %multi;
38             @{$multi{server}}{qw(realm auth-param)} = ();
39             @{$multi{client}}{qw()} = ();
40              
41             my @server_required = qw(algorithm nonce);
42             my @client_required = qw(username nonce cnonce nc qop response);
43              
44             # available ciphers
45             my @ourciphers = (
46             {
47             name => 'rc4',
48             ssf => 128,
49             bs => 1,
50             ks => 16,
51             pkg => 'Crypt::RC4',
52             key => sub { $_[0] },
53             iv => sub {},
54             fixup => sub {
55             # retrofit the Crypt::RC4 module with standard subs
56             *Crypt::RC4::encrypt = *Crypt::RC4::decrypt =
57             sub { goto &Crypt::RC4::RC4; };
58             *Crypt::RC4::keysize = sub {128};
59             *Crypt::RC4::blocksize = sub {1};
60             }
61             },
62             {
63             name => '3des',
64             ssf => 112,
65             bs => 8,
66             ks => 16,
67             pkg => 'Crypt::DES3',
68             key => sub {
69             pack('B8' x 16,
70             map { $_ . '0' }
71             map { unpack('a7' x 16, $_); }
72             unpack('B*', substr($_[0], 0, 14)) );
73             },
74             iv => sub { substr($_[0], -8, 8) },
75             },
76             {
77             name => 'des',
78             ssf => 56,
79             bs => 8,
80             ks => 16,
81             pkg => 'Crypt::DES',
82             key => sub {
83             pack('B8' x 8,
84             map { $_ . '0' }
85             map { unpack('a7' x 8, $_); }
86             unpack('B*',substr($_[0], 0, 7)) );
87             },
88             iv => sub { substr($_[0], -8, 8) },
89             },
90             {
91             name => 'rc4-56',
92             ssf => 56,
93             bs => 1,
94             ks => 7,
95             pkg => 'Crypt::RC4',
96             key => sub { $_[0] },
97             iv => sub {},
98             fixup => sub {
99             *Crypt::RC4::encrypt = *Crypt::RC4::decrypt =
100             sub { goto &Crypt::RC4::RC4; };
101             *Crypt::RC4::keysize = sub {56};
102             *Crypt::RC4::blocksize = sub {1};
103             }
104             },
105             {
106             name => 'rc4-40',
107             ssf => 40,
108             bs => 1,
109             ks => 5,
110             pkg => 'Crypt::RC4',
111             key => sub { $_[0] },
112             iv => sub {},
113             fixup => sub {
114             *Crypt::RC4::encrypt = *Crypt::RC4::decrypt =
115             sub { goto &Crypt::RC4::RC4; };
116             *Crypt::RC4::keysize = sub {40};
117             *Crypt::RC4::blocksize = sub {1};
118             }
119             },
120             );
121              
122             ## The system we are on, might not be able to crypt the stream
123             our $NO_CRYPT_AVAILABLE = 1;
124             for (@ourciphers) {
125             eval "require $_->{pkg}";
126             unless ($@) {
127             $NO_CRYPT_AVAILABLE = 0;
128             last;
129             }
130             }
131              
132 16     16   31 sub _order { 3 }
133             sub _secflags {
134 12     12   24 shift;
135 12         38 scalar grep { $secflags{$_} } @_;
  8         55  
136             }
137              
138 9     9 0 1852 sub mechanism { 'DIGEST-MD5' }
139              
140             sub _init {
141 20     20   52 my ($pkg, $self) = @_;
142 20         39 bless $self, $pkg;
143              
144             # set default security properties
145 20         77 $self->property('minssf', 0);
146 20         62 $self->property('maxssf', int 2**31 - 1); # XXX - arbitrary "high" value
147 20         57 $self->property('maxbuf', 0xFFFFFF); # maximum supported by GSSAPI mech
148 20         54 $self->property('externalssf', 0);
149              
150 20         81 $self;
151             }
152              
153             sub _init_server {
154 8     8   16 my $server = shift;
155 8   100     29 my $options = shift || {};
156 8 50 33     74 if (!ref $options or ref $options ne 'HASH') {
157 0         0 warn "options for DIGEST_MD5 should be a hashref";
158 0         0 $options = {};
159             }
160              
161             ## new server, means new nonce_counts
162 8         20 $server->{nonce_counts} = {};
163              
164             ## determine supported qop
165 8         22 my @qop = ('auth');
166 8 100       28 push @qop, 'auth-int' unless $options->{no_integrity};
167             push @qop, 'auth-conf' unless $options->{no_integrity}
168             or $options->{no_confidentiality}
169 8 50 66     46 or $NO_CRYPT_AVAILABLE;
      33        
170              
171 8         18 $server->{supported_qop} = { map { $_ => 1 } @qop };
  15         65  
172             }
173              
174             sub init_sec_layer {
175 16     16 0 42 my $self = shift;
176 16         49 $self->{cipher} = undef;
177 16         30 $self->{khc} = undef;
178 16         27 $self->{khs} = undef;
179 16         28 $self->{sndseqnum} = 0;
180 16         38 $self->{rcvseqnum} = 0;
181              
182             # reset properties for new session
183 16         50 $self->property(maxout => undef);
184 16         38 $self->property(ssf => undef);
185             }
186              
187             # no initial value passed to the server
188             sub client_start {
189 7     7 0 22 my $self = shift;
190              
191 7         12 $self->{need_step} = 1;
192 7         13 $self->{error} = undef;
193 7         18 $self->{state} = 0;
194 7         20 $self->init_sec_layer;
195 7         19 '';
196             }
197              
198             sub server_start {
199 9     9 0 598 my $self = shift;
200 9         15 my $challenge = shift;
201 9   100 3   35 my $cb = shift || sub {};
202              
203 9         19 $self->{need_step} = 1;
204 9         19 $self->{error} = undef;
205 9   66     132 $self->{nonce} = md5_hex($NONCE || join (":", $$, time, rand));
206              
207 9         29 $self->init_sec_layer;
208              
209 9         15 my $qop = [ sort keys %{$self->{supported_qop}} ];
  9         45  
210              
211             ## get the realm using callbacks but default to the host specified
212             ## during the instantiation of the SASL object
213 9         42 my $realm = $self->_call('realm');
214 9   33     64 $realm ||= $self->host;
215              
216             my %response = (
217             nonce => $self->{nonce},
218             charset => 'utf-8',
219             algorithm => 'md5-sess',
220             realm => $realm,
221             maxbuf => $self->property('maxbuf'),
222              
223             ## IN DRAFT ONLY:
224             # If this directive is present multiple times the client MUST treat
225             # it as if it received a single qop directive containing a comma
226             # separated value from all instances. I.e.,
227             # 'qop="auth",qop="auth-int"' is the same as 'qop="auth,auth-int"
228              
229             'qop' => $qop,
230 9         26 'cipher' => [ map { $_->{name} } @ourciphers ],
  45         171  
231             );
232 9         31 my $final_response = _response(\%response);
233 9         37 $cb->($final_response);
234 9         53 return;
235             }
236              
237             sub client_step { # $self, $server_sasl_credentials
238 11     11 0 40 my ($self, $challenge) = @_;
239 11         29 $self->{server_params} = \my %sparams;
240              
241             # Parse response parameters
242             $self->_parse_challenge(\$challenge, server => $self->{server_params})
243 11 50       32 or return $self->set_error("Bad challenge: '$challenge'");
244              
245 11 100       29 if ($self->{state} == 1) {
246             # check server's `rspauth' response
247             return $self->set_error("Server did not send rspauth in step 2")
248 4 50       12 unless ($sparams{rspauth});
249             return $self->set_error("Invalid rspauth in step 2")
250 4 50       17 unless ($self->{rspauth} eq $sparams{rspauth});
251              
252             # all is well
253 4         365 $self->set_success;
254 4         12 return '';
255             }
256              
257             # check required fields in server challenge
258 7 50       17 if (my @missing = grep { !exists $sparams{$_} } @server_required) {
  14         45  
259 0         0 return $self->set_error("Server did not provide required field(s): @missing")
260             }
261              
262             my %response = (
263             nonce => $sparams{'nonce'},
264             cnonce => md5_hex($CNONCE || join (":", $$, time, rand)),
265             'digest-uri' => $self->service . '/' . $self->host,
266             # calc how often the server nonce has been seen; server expects "00000001"
267             nc => sprintf("%08d", ++$self->{nonce_counts}{$sparams{'nonce'}}),
268 7   66     95 charset => $sparams{'charset'},
269             );
270              
271 7 50       25 return $self->set_error("Server qop too weak (qop = $sparams{'qop'})")
272             unless ($self->_client_layer(\%sparams,\%response));
273              
274             # let caller-provided fields override defaults: authorization ID, service name, realm
275              
276 7   50     21 my $s_realm = $sparams{realm} || [];
277 7         27 my $realm = $self->_call('realm', @$s_realm);
278 7 50       18 unless (defined $realm) {
279             # If the user does not pick a realm, use the first from the server
280 7         14 $realm = $s_realm->[0];
281             }
282 7 50       21 if (defined $realm) {
283 7         14 $response{realm} = $realm;
284             }
285              
286 7         18 my $authzid = $self->_call('authname');
287 7 100       19 if (defined $authzid) {
288 2         6 $response{authzid} = $authzid;
289             }
290              
291 7         23 my $serv_name = $self->_call('serv');
292 7 50       19 if (defined $serv_name) {
293 0         0 $response{'digest-uri'} .= '/' . $serv_name;
294             }
295              
296 7         16 my $user = $self->_call('user');
297 7 50       17 return $self->set_error("Username is required")
298             unless defined $user;
299 7         21 $response{username} = $user;
300              
301 7         19 my $password = $self->_call('pass');
302 7 50       24 return $self->set_error("Password is required")
303             unless defined $password;
304              
305 7   100     31 $self->property('maxout', $sparams{maxbuf} || 65536);
306              
307             # Generate the response value
308 7         14 $self->{state} = 1;
309              
310 7         24 my ($response, $rspauth)
311             = $self->_compute_digests_and_set_keys($password, \%response);
312              
313 7         14 $response{response} = $response;
314 7         14 $self->{rspauth} = $rspauth;
315              
316             # finally, return our response token
317 7         24 return _response(\%response, "is_client");
318             }
319              
320             sub _compute_digests_and_set_keys {
321 12     12   23 my $self = shift;
322 12         21 my $password = shift;
323 12         18 my $params = shift;
324              
325 12 50 33     70 if (defined $params->{realm} and ref $params->{realm} eq 'ARRAY') {
326 0         0 $params->{realm} = $params->{realm}[0];
327             }
328              
329 12         23 my $realm = $params->{realm};
330 12 50       30 $realm = "" unless defined $realm;
331              
332             my $A1 = join (":",
333             md5(join (":", $params->{username}, $realm, $password)),
334             @$params{defined($params->{authzid})
335 12 100       107 ? qw(nonce cnonce authzid)
336             : qw(nonce cnonce)
337             }
338             );
339              
340             # pre-compute MD5(A1) and HEX(MD5(A1)); these are used multiple times below
341 12         73 my $hdA1 = unpack("H*", (my $dA1 = md5($A1)) );
342              
343             # derive keys for layer encryption / integrity
344 12         49 $self->{kic} = md5($dA1,
345             'Digest session key to client-to-server signing key magic constant');
346              
347 12         44 $self->{kis} = md5($dA1,
348             'Digest session key to server-to-client signing key magic constant');
349              
350 12 50       34 if (my $cipher = $self->{cipher}) {
351 0 0   0   0 &{ $cipher->{fixup} || sub{} };
  0         0  
352              
353             # compute keys for encryption
354 0         0 my $ks = $cipher->{ks};
355 0         0 $self->{kcc} = md5(substr($dA1,0,$ks),
356             'Digest H(A1) to client-to-server sealing key magic constant');
357 0         0 $self->{kcs} = md5(substr($dA1,0,$ks),
358             'Digest H(A1) to server-to-client sealing key magic constant');
359              
360             # get an encryption and decryption handle for the chosen cipher
361 0         0 $self->{khc} = $cipher->{pkg}->new($cipher->{key}->($self->{kcc}));
362 0         0 $self->{khs} = $cipher->{pkg}->new($cipher->{key}->($self->{kcs}));
363              
364             # initialize IVs
365 0         0 $self->{ivc} = $cipher->{iv}->($self->{kcc});
366 0         0 $self->{ivs} = $cipher->{iv}->($self->{kcs});
367             }
368              
369 12         27 my $A2 = "AUTHENTICATE:" . $params->{'digest-uri'};
370 12 100       31 $A2 .= ":00000000000000000000000000000000" if ($params->{qop} ne 'auth');
371              
372             my $response = md5_hex(
373 12         84 join (":", $hdA1, @$params{qw(nonce nc cnonce qop)}, md5_hex($A2))
374             );
375              
376             # calculate server `rspauth' response, so we can check in step 2
377             # the only difference here is in the A2 string which from which
378             # `AUTHENTICATE' is omitted in the calculation of `rspauth'
379 12         31 $A2 = ":" . $params->{'digest-uri'};
380 12 100       31 $A2 .= ":00000000000000000000000000000000" if ($params->{qop} ne 'auth');
381              
382             my $rspauth = md5_hex(
383 12         67 join (":", $hdA1, @$params{qw(nonce nc cnonce qop)}, md5_hex($A2))
384             );
385              
386 12         45 return ($response, $rspauth);
387             }
388              
389             sub server_step {
390 8     8 0 30 my $self = shift;
391 8         18 my $challenge = shift;
392 8   100 2   26 my $cb = shift || sub {};
393              
394 8         30 $self->{client_params} = \my %cparams;
395 8 100       26 unless ( $self->_parse_challenge(\$challenge, client => $self->{client_params}) ) {
396 1         5 $self->set_error("Bad challenge: '$challenge'");
397 1         3 return $cb->();
398             }
399              
400             # check required fields in server challenge
401 7 50       18 if (my @missing = grep { !exists $cparams{$_} } @client_required) {
  42         131  
402 0         0 $self->set_error("Client did not provide required field(s): @missing");
403 0         0 return $cb->();
404             }
405              
406 7   50     29 my $count = hex ($cparams{'nc'} || 0);
407 7 50       38 unless ($count == ++$self->{nonce_counts}{$cparams{nonce}}) {
408 0         0 $self->set_error("nonce-count doesn't match: $count");
409 0         0 return $cb->();
410             }
411              
412 7   50     22 my $qop = $cparams{'qop'} || "auth";
413 7 100       20 unless ($self->is_qop_supported($qop)) {
414 1         10 $self->set_error("Client qop not supported (qop = '$qop')");
415 1         3 return $cb->();
416             }
417              
418 6         14 my $username = $cparams{'username'};
419 6 50       14 unless ($username) {
420 0         0 $self->set_error("Client didn't provide a username");
421 0         0 return $cb->();
422             }
423              
424             # "The authzid MUST NOT be an empty string."
425 6 50 66     23 if (exists $cparams{authzid} && $cparams{authzid} eq '') {
426 0         0 $self->set_error("authzid cannot be empty");
427 0         0 return $cb->();
428             }
429 6         19 my $authzid = $cparams{authzid};
430              
431             # digest-uri: "Servers SHOULD check that the supplied value is correct.
432             # This will detect accidental connection to the incorrect server, as well as
433             # some redirection attacks"
434 6         11 my $digest_uri = $cparams{'digest-uri'};
435 6         25 my ($cservice, $chost, $cservname) = split '/', $digest_uri, 3;
436 6 100 66     24 if ($cservice ne $self->service or $chost ne $self->host) {
437             # XXX deal with serv_name
438 1         4 $self->set_error("Incorrect digest-uri");
439 1         11 return $cb->();
440             }
441              
442 5 50       25 unless (defined $self->callback('getsecret')) {
443 0         0 $self->set_error("a getsecret callback MUST be defined");
444 0         0 $cb->();
445 0         0 return;
446             }
447              
448 5         13 my $realm = $self->{client_params}->{'realm'};
449             my $response_check = sub {
450 5     5   11 my $password = shift;
451 5 50       15 return $self->set_error("Cannot get the passord for $username")
452             unless defined $password;
453            
454             ## configure the security layer
455 5 50       14 $self->_server_layer($qop)
456             or return $self->set_error("Cannot negociate the security layer");
457            
458             my ($expected, $rspauth)
459 5         16 = $self->_compute_digests_and_set_keys($password, $self->{client_params});
460            
461             return $self->set_error("Incorrect response $self->{client_params}->{response} <> $expected")
462 5 100       27 unless $expected eq $self->{client_params}->{response};
463            
464 3         10 my %response = (
465             rspauth => $rspauth,
466             );
467            
468             # I'm not entirely sure of what I am doing
469 3         19 $self->{answer}{$_} = $self->{client_params}->{$_} for qw/username authzid realm serv/;
470            
471 3         19 $self->set_success;
472 3         8 return _response(\%response);
473 5         25 };
474              
475             $self->callback('getsecret')->(
476             $self,
477             { user => $username, realm => $realm, authzid => $authzid },
478 5     5   1812 sub { $cb->( $response_check->( shift ) ) },
479 5         32 );
480             }
481              
482             sub is_qop_supported {
483 7     7 0 16 my $self = shift;
484 7         9 my $qop = shift;
485 7         22 return $self->{supported_qop}{$qop};
486             }
487              
488             sub _response {
489 19     19   33 my $response = shift;
490 19         29 my $is_client = shift;
491              
492 19         33 my @out;
493 19         126 for my $k (sort keys %$response) {
494 131   66     325 my $is_array = ref $response->{$k} && ref $response->{$k} eq 'ARRAY';
495 131 100       267 my @values = $is_array ? @{$response->{$k}} : ($response->{$k});
  18         46  
496             # Per spec, one way of doing it: multiple k=v
497             #push @out, [$k, $_] for @values;
498             # other way: comma separated list
499 131         408 push @out, [$k, join (',', @values)];
500             }
501 19         50 return join (",", map { _qdval($_->[0], $_->[1], $is_client) } @out);
  131         256  
502             }
503              
504             sub _parse_challenge {
505 19     19   42 my $self = shift;
506 19         28 my $challenge_ref = shift;
507 19         30 my $type = shift;
508 19         26 my $params = shift;
509              
510 19         193 while($$challenge_ref =~
511             s/^(?:\s*,)*\s* # remaining or crap
512             ([\w-]+) # key, eg: qop
513             =
514             ("([^\\"]+|\\.)*"|[^,]+) # value, eg: auth-conf or "NoNcE"
515             \s*(?:,\s*)* # remaining
516             //x) {
517              
518 117         351 my ($k, $v) = ($1,$2);
519 117 100       336 if ($v =~ /^"(.*)"$/s) {
520 65         149 ($v = $1) =~ s/\\(.)/$1/g;
521             }
522 117 100       315 if (exists $multi{$type}{$k}) {
    100          
523 7   50     35 my $aref = $params->{$k} ||= [];
524 7         38 push @$aref, $v;
525             }
526             elsif (defined $params->{$k}) {
527 1         7 return $self->set_error("Bad challenge: '$$challenge_ref'");
528             }
529             else {
530 109         668 $params->{$k} = $v;
531             }
532             }
533 18 50       66 return length $$challenge_ref ? 0 : 1;
534             }
535              
536             sub _qdval {
537 131     131   244 my ($k, $v, $is_client) = @_;
538              
539 131 100       240 my $qdval = $is_client ? \%cqdval : \%sqdval;
540              
541 131 50       309 if (!defined $v) {
    100          
542 0         0 return;
543             }
544             elsif (exists $qdval->{$k}) {
545 73         161 $v =~ s/([\\"])/\\$1/g;
546 73         301 return qq{$k="$v"};
547             }
548              
549 58         194 return "$k=$v";
550             }
551              
552             sub _server_layer {
553 5     5   12 my ($self, $auth) = @_;
554              
555             # XXX dupe
556             # construct our qop mask
557 5         21 my $maxssf = $self->property('maxssf') - $self->property('externalssf');
558 5 50       17 $maxssf = 0 if ($maxssf < 0);
559 5         13 my $minssf = $self->property('minssf') - $self->property('externalssf');
560 5 50       23 $minssf = 0 if ($minssf < 0);
561              
562 5 50       13 return undef if ($maxssf < $minssf); # sanity check
563              
564 5         12 my $ciphers = [ map { $_->{name} } @ourciphers ];
  25         50  
565 5 50 33     17 if (( $auth eq 'auth-conf')
566             and $self->_select_cipher($minssf, $maxssf, $ciphers )) {
567 0         0 $self->property('ssf', $self->{cipher}->{ssf});
568 0         0 return 1;
569             }
570 5 100       12 if ($auth eq 'auth-int') {
571 3         10 $self->property('ssf', 1);
572 3         10 return 1;
573             }
574 2 50       17 if ($auth eq 'auth') {
575 2         8 $self->property('ssf', 0);
576 2         8 return 1;
577             }
578              
579 0         0 return undef;
580             }
581              
582             sub _client_layer {
583 7     7   25 my ($self, $sparams, $response) = @_;
584              
585             # construct server qop mask
586             # qop in server challenge is optional: if not there "auth" is assumed
587 7         14 my $smask = 0;
588             map {
589 13 100       44 m/^auth$/ and $smask |= 1;
590 13 100       41 m/^auth-int$/ and $smask |= 2;
591 13 50       39 m/^auth-conf$/ and $smask |= 4;
592 7   50     29 } split(/,/, $sparams->{qop}||'auth'); # XXX I think we might have a bug here bc. of LWS
593              
594             # construct our qop mask
595 7         14 my $cmask = 0;
596 7         26 my $maxssf = $self->property('maxssf') - $self->property('externalssf');
597 7 50       20 $maxssf = 0 if ($maxssf < 0);
598 7         15 my $minssf = $self->property('minssf') - $self->property('externalssf');
599 7 50       29 $minssf = 0 if ($minssf < 0);
600              
601 7 50       18 return undef if ($maxssf < $minssf); # sanity check
602              
603             # ssf values > 1 mean integrity and confidentiality
604             # ssf == 1 means integrity but no confidentiality
605             # ssf < 1 means neither integrity nor confidentiality
606             # no security layer can be had if buffer size is 0
607 7 50       22 $cmask |= 1 if ($minssf < 1);
608 7 50 33     44 $cmask |= 2 if ($minssf <= 1 and $maxssf >= 1);
609 7 50       18 $cmask |= 4 if ($maxssf > 1);
610              
611             # find common bits
612 7         10 $cmask &= $smask;
613              
614             # parse server cipher options
615 7   100     66 my @sciphers = split(/,/, $sparams->{'cipher-opts'}||$sparams->{cipher}||'');
616              
617 7 50 33     45 if (($cmask & 4) and $self->_select_cipher($minssf,$maxssf,\@sciphers)) {
618 0         0 $response->{qop} = 'auth-conf';
619 0         0 $response->{cipher} = $self->{cipher}->{name};
620 0         0 $self->property('ssf', $self->{cipher}->{ssf});
621 0         0 return 1;
622             }
623 7 100       24 if ($cmask & 2) {
624 4         9 $response->{qop} = 'auth-int';
625 4         12 $self->property('ssf', 1);
626 4         14 return 1;
627             }
628 3 50       9 if ($cmask & 1) {
629 3         7 $response->{qop} = 'auth';
630 3         11 $self->property('ssf', 0);
631 3         11 return 1;
632             }
633              
634 0           return undef;
635             }
636              
637             sub _select_cipher {
638 0     0     my ($self, $minssf, $maxssf, $ciphers) = @_;
639              
640             # compose a subset of candidate ciphers based on ssf and peer list
641             my @a = map {
642 0           my $c = $_;
  0            
643             (grep { $c->{name} eq $_ } @$ciphers and
644 0 0 0       $c->{ssf} >= $minssf and $c->{ssf} <= $maxssf) ? $_ : ()
645             } @ourciphers;
646              
647             # from these, select the first one we can create an instance of
648 0           for (@a) {
649 0 0         next unless eval "require $_->{pkg}";
650 0           $self->{cipher} = $_;
651 0           return 1;
652             }
653              
654 0           return 0;
655             }
656              
657 5     5   52 use Digest::HMAC_MD5 qw(hmac_md5);
  5         10  
  5         3367  
658              
659             sub encode { # input: self, plaintext buffer,length (length not used here)
660 0     0 0   my $self = shift;
661 0           my $seqnum = pack('N', $self->{sndseqnum}++);
662 0           my $mac = substr(hmac_md5($seqnum . $_[0], $self->{kic}), 0, 10);
663              
664             # if integrity only, return concatenation of buffer, MAC, TYPE and SEQNUM
665 0 0         return $_[0] . $mac.pack('n',1) . $seqnum unless ($self->{khc});
666              
667             # must encrypt, block ciphers need padding bytes
668 0           my $pad = '';
669 0           my $bs = $self->{cipher}->{bs};
670 0 0         if ($bs > 1) {
671             # padding is added in between BUF and MAC
672 0           my $n = $bs - ((length($_[0]) + 10) & ($bs - 1));
673 0           $pad = chr($n) x $n;
674             }
675              
676             # XXX - for future AES cipher support, the currently used common _crypt()
677             # function probably wont do; we might to switch to per-cipher routines
678             # like so:
679             # return $self->{khc}->encrypt($_[0] . $pad . $mac) . pack('n', 1) . $seqnum;
680 0           return $self->_crypt(0, $_[0] . $pad . $mac) . pack('n', 1) . $seqnum;
681             }
682              
683             sub decode { # input: self, cipher buffer,length
684 0     0 0   my ($self, $buf, $len) = @_;
685              
686 0 0         return if ($len <= 16);
687              
688             # extract TYPE/SEQNUM from end of buffer
689 0           my ($type,$seqnum) = unpack('na[4]', substr($buf, -6, 6, ''));
690              
691             # decrypt remaining buffer, if necessary
692 0 0         if ($self->{khs}) {
693             # XXX - see remark above in encode() #$buf = $self->{khs}->decrypt($buf);
694 0           $buf = $self->_crypt(1, $buf);
695             }
696 0 0         return unless ($buf);
697              
698             # extract 10-byte MAC from the end of (decrypted) buffer
699 0           my ($mac) = unpack('a[10]', substr($buf, -10, 10, ''));
700              
701 0 0 0       if ($self->{khs} and $self->{cipher}->{bs} > 1) {
702             # remove padding
703 0           my $n = ord(substr($buf, -1, 1));
704 0           substr($buf, -$n, $n, '');
705             }
706              
707             # check the MAC
708 0           my $check = substr(hmac_md5($seqnum . $buf, $self->{kis}), 0, 10);
709 0 0         return if ($mac ne $check);
710 0 0         return if (unpack('N', $seqnum) != $self->{rcvseqnum});
711 0           $self->{rcvseqnum}++;
712              
713 0           return $buf;
714             }
715              
716             sub _crypt { # input: op(decrypting=1/encrypting=0)), buffer
717 0     0     my ($self,$d) = (shift,shift);
718 0           my $bs = $self->{cipher}->{bs};
719              
720 0 0         if ($bs <= 1) {
721             # stream cipher
722 0 0         return $d ? $self->{khs}->decrypt($_[0]) : $self->{khc}->encrypt($_[0])
723             }
724              
725             # the remainder of this sub is for block ciphers
726              
727             # get current IV
728 0 0         my $piv = \$self->{$d ? 'ivs' : 'ivc'};
729 0           my $iv = $$piv;
730              
731             my $result = join '', map {
732 0           my $x = $d
733             ? $iv ^ $self->{khs}->decrypt($_)
734 0 0         : $self->{khc}->encrypt($iv ^ $_);
735 0 0         $iv = $d ? $_ : $x;
736 0           $x;
737             } unpack("a$bs "x(int(length($_[0])/$bs)), $_[0]);
738              
739             # store current IV
740 0           $$piv = $iv;
741 0           return $result;
742             }
743              
744             1;
745              
746             __END__