File Coverage

blib/lib/Authen/SASL/Perl/DIGEST_MD5.pm
Criterion Covered Total %
statement 236 316 74.6
branch 82 152 53.9
condition 30 61 49.1
subroutine 28 33 84.8
pod 0 9 0.0
total 376 571 65.8


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