File Coverage

blib/lib/Authen/SASL/Perl/DIGEST_MD5.pm
Criterion Covered Total %
statement 232 313 74.1
branch 78 148 52.7
condition 35 65 53.8
subroutine 26 31 83.8
pod 0 9 0.0
total 371 566 65.5


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